In every project you have to deal with apis and their implementations. While a lot of projects separate the api from it’s implementation through different java packages (mostly named internal or impl), I want to show you another approach and tell you why this approach makes more sense. I call this other approach “implementation separation by packaging artifact”.
Let’s take a look at a widly used project structure that separates the api from the implementation by using a separate impl package.
This kind of separation is widely used but it has a some impacts on design and api usage as I will show now.
Api and implementation dependencies differ
The purpose of an api is to decouple the client that uses the api from the concrete implementation technology. Therefore the api defines other dependencies as the implementation. In the example above the api and the implementation belong to the same maven project. But maven dependencies are defined in the pom.xml and therefore per artifact. The resulting artifact (or jar file in our case) will contain the api classes as well as the implementation classes. The artifact belongs to the pom that defines the dependencies and I guess you don’t want your api to have the same dependencies as your implementation, do you?
The api for example might only depend on pure java while the implementation might use a framework like spring or hibernate.
Let’s think about the client that wants to use the api. If the client wants to integrate that api it must depend on the api, but currently the client will also depend on all the implementation depdendencies. Some developers might argue now that you can use the maven-assembly-plugin to create an artifact with only the api classes, but keep in mind that this artifact is only attached to the pom. So even if you use the maven-assebly-plugin to create different archives for the api and implementation both still belong to the same pom and this means that both still have the same dependencies. The next project example shows that a client will also get all implementation dependencies if you put api and implementation together in one maven project.
Make the api contract explicit
If you separate the implementation from the api by using java packages you remove the posibillity to use the java package access modifier which is a powerful language feature. Let’s take a look at some source code to understand the problem:
package com.link_intersystems.examples.api.impl; public class ServiceImpl implements Service { public ServiceResponse doService(ServiceRequest serviceRequest) { // do the service ServiceResponse response = new ServiceResponse(); return response; } }
package com.link_intersystems.examples.api; public class ServiceResponse{ public ServiceResponse(){ } } package com.link_intersystems.examples.api; public interface Service { public ServiceResponse doService(ServiceRequest serviceRequest); }
The ServiceRequest and ServiceResponse types are part of the Service‘s api and therefore should be placed in the same package. The ServiceImpl must instantiate a ServiceResponse object, because it must return one. But the ServiceImpl is located in another package than the ServiceResponse type. Therefore the ServiceResponse‘s constructor must be public. Making it public also makes it public for all other classes and that increases the possibility that it is used in another context somewhere else for which it has not been designed. It would be better to make it package scope, but we can’t do this as long as the ServiceImpl is in another package.
Maybe you might think that it is no big deal, because the name ServiceResponse makes it clear that noone else should instantiate it. But would it also be so clear if the response type is named OrderTO?
Why don’t we express the way an object should be used in our source code? If we do so we make an implicit contract explicit and explicit contracts are checked by the compiler. That makes software clear and programming less error-prone. So everytime you have the possibility to express the api contract using access modifiers, interfaces, classes or what else – do it.
Some developers argue that this way to express the api contract takes flexibility away because of the access modifier restrictions. Does it really take flexibility away? The class was never designed to be used in another context. So why would someone use it? To get worse using it in another context couples both context to each other and a change to the shared class can easily break it’s meaning in the other context. Making everything public can easily lead to breaking layer boundaries like using domain objects as ui models or ui models as transfer objects and so on.
Prefer a clear spearation of api and implementation
Fortuanatly we can easily get rid of all the pitfalls that are described in the sections before by using two maven projects. One for the api and one for the implementation.
The service api as well as the service implementation belong to the same package (or subsystem). Therefore we can instantiate the ServiceResponse within the service impl even it has only package access. The ServiceResponse type is still public because clients of the service must see it.
This separation prevents the client from getting all dependencies of the implementation.
I hope I could show why the separation of the api and implementation is important.
Great article, very detailed. One should always use the compiler design approaches for helping to get cleaner code. Thank you.
“Oldie but Goldie”! ! knew the concept, but I had forgot the details and the implementation… Thanks for the great summary.
Excellent and still a very valid concept. Many Thanks. Where do you place the model classes that are required by API and IMPL at the same time: In the API project or in a separate model project?
Usually in the API, because the API is using these classes. So they belong to the abtract concept – the API. The IMPL always depends on the API, so it can also access these model classes. Keep in mind that the API and the IMPL can be defined within the same java package. Thus the IMPL can access members of the model that are not visible to clients – the package scope. E.g. a service’s response model can have a package scoped constructor, so that only the service impl can create these objects.