Libraries & Configurations

Surendra Raut
3 min readJun 6, 2021
Photo by Ferenc Almasi on Unsplash

Libraries and configurations offer functionality & flexibility respectively. When combined, their effectiveness multifolds. However, a tight coupling between the two can lead to problem which might quickly negates all the benefits. Let’s take a look at this problem and explore a way to avoid it.

Code snippets are based on Spring framework.

Problem —

Assume we are writing a library which adds 2 http headers (Authorization & x-application-name) whenever a REST api call is made. Here is an implementation of the service.

@Component
public class AppHttpClientInterceptor implements ClientHttpRequestInterceptor {
private static final APPLICATION_NAME = "x-application-name";

@Value("${http.token}")
private String token;

@Value("${application.name}")
private String applicationName;

@Override
public ClientHttpResponse intercept(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().setBearerAuth(token);
request.getHeaders()
.set(APPLICATION_NAME, applicationName);
return execution.execute(request, body);
}
}

This service needs few configurations (i.e. token & application name) from Spring based application properties. Since framework is dependent on properties mentioned in application.yml, there is a tight coupling between framework & properties from application.yml.

What’s wrong with tight coupling?

Simply put, less felxibility & more re-work. Let’s take a look at some of the draw-

1. Assumption of Spring framework -

Library assumes application is going to be a Spring framework based application. If you try to use it for non-Spring application, it will fail.

2. Configuration layer dependency -

Library assums that configurations will always be stored in application.yml file. If that changes to environment variables or database, underlying library will break and needs code change.

3. Fixed configuration keys -

Hardcoded property names can lead to conflict with application or another library. Imagine your application has another dependency which relies on exact name for some other purpose.

How do we fix it?

This tight coupling can be solved by adding a level of abstraction. Library does/should not care how configuration is read or what name it follows. All it should care is somehow application is able to provide it.

Abstration layer can be implemented using interface or abstract class. Let’s take a look at an interface exposed by library.

public interface RestApiConfig {

String getToken();

String getApplicationName();
}

Here is modified service implementation which uses interface to fetch required configurations.

@Component
public class AppHttpClientInterceptor implements ClientHttpRequestInterceptor {
private static final APPLICATION_NAME = "x-application-name";

private RestApiConfig restApiConfig;
@Autowired
public AppHttpClientInterceptor(RestApiConfig restApiConfig) {
this.restApiConfig = restApiConfig;
}
@Override
public ClientHttpResponse intercept(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().setBearerAuth(restApiConfig.getToken());
request.getHeaders()
.set(APPLICATION_NAME, restApiConfig.getApplicationName());
return execution.execute(request, body);
}
}

Remember, library only provides abstraction in the form of interface. Application that uses this library needs to provide implementation.

With this layer of abstraction, even application has freedom to choose any way it feels feasible to store its configurations. All application needs to do is implement the interface. Below are some of the examples, but it is not limited to it.

application.yml -

@Configuration
public class YamlRestApiConfig implements RestApiConfig {

@Value("${http.token}")
private String token;

@Value("${application.name}")
private String applicationName;

@Override
public String getToken() {
return token;
}

@Override
public String getApplicationName() {
return applicationName;
}
}

environment variables -

@Configuration
public class EnvRestApiConfig implements RestApiConfig {

public static final String TOKEN_KEY = "token";
public static final String APPLICATION_NAME_KEY = "applicationName";

@Override
public String getToken() {
return System.getenv(TOKEN_KEY);
}

@Override
public String getApplicationName() {
return System.getenv(APPLICATION_NAME_KEY);
}
}

As a developer of a library, we should focus on providing as much generic, flexible functionality as we can. By adding this loose coupling, we are doing exactly that.

I hope I was successful in convincing you the point. In case you have any question/suggesstions, please feel free to reach out to me surendra.raut@gmail.com.

--

--