What's a good pattern for separating API from implementation?


(Etienne Studer) #1

Hello

I have a Gradle project that builds services, i.e. service api classes and service implementation classes. I now want to separate the api classes from the implementation classes. What is an ‘elegant’ and suggested way to do this with Gradle?

I have the following requirements: - have a jar that contains only the service api classes (and no dependency on implementation classes) -> classifier api - have a jar that contains only the implementation classes (and a dependency to the api classes) -> classifier impl - have a jar that contains both api and implementation classes (that’s what I currently have) -> no classifier - upload all three jars to the the same Maven repo location (their names differ only by the classifier)

Thoughts: - should I have an additional configuration like mainApi? (could I then still have other Gradle projects depend on just this mainApi configuration?) - should I have another project with just the services? (but then how to do the upload of the three jars to the same repo location?)

Regards, Etienne


(Luke Daley) #2

if it were me, I wouldn’t use classifiers at all as these are logically 3 different things. I’m also unsure why you’d want a fat jar with the api and impl.

I generally take the stance that classifiers should only be used for metadata about the same logical entity (e.g. source, docs etc).

If you can avoid the classifier route, you take away all the special handling needs as these just become projects in their own right and you can just manage them as per normal projects.

I think you’ll save yourself a lot of pain avoiding classifiers, but post back if you do want to forge ahead with them and we’ll see what can be done.


(Etienne Studer) #3

Hi Luke

Thanks for your thoughts.

To avoid any confusion, by API I mean Java interfaces and by impl I mean implementation of these interfaces.

The reason we have a ‘fat’ jar is that so far, there has never been the need to physically separate these two from each other (but now there is a customer that requires it for classloading reasons). And, there is quite some overhead in separating them into different projects with their own builds files, etc. Also, I’ve made the experience that it is more convenient for users to just include one jar vs. two.

If I look at Spring, a project for which I have very high respect, they do not ship different jars for their interfaces and their implementations.

Alright, these were just my thoughts on why we have not physically separated api from impl so far. But, since I now need to do it, I think I will try to separate them into different projects, as you suggest.


(Luke Daley) #4

Hi Etienne,

You don’t need to necessarily split them into separate Gradle projects to publish multiple jars. You could still publish multiple jars out of the one project like you were planning to do with the multiple classifiers approach, but just give each jar it’s own artifact id.


(Etienne Studer) #5

Ok, I think I’m starting to get what you mean… So, I imagine that

  1. I should have two new configurations ‘api’ and ‘impl’, assign them new source directories, and not make use of the compile configuration anymore. The ‘impl’ config should then use the ‘api’ config (just like test uses compile)

  2. Create different artifacts for these different configurations (as people discussed in a recent post)

I wonder if I can properly code this. Could you give me a hand with this? I think this could become useful to other people as well.


(john.b.hurst) #6

I have very high respect for Spring too, but their JAR packaging is not ideal and should not be emulated!

For example, spring-orm contains implementations relating to

hibernate3

ibatis

jdo

jpa

with the jpa including vendor-specific support for

EclipseLink

Hibernate

OpenJpa

TopLink

Few projects would require all of this together, and yet the packaging of Spring bundles it all together.

John Hurst Wellington, New Zealand


(Etienne Studer) #7

By now, I believe that separating my project into an api jar and a impl jar is the way to go for my specific use case. So, the point that I’m currently at, is that I need to define my custom Gradle configurations for api and impl and make sure they are defined properly regarding their relationship, inheritance, upload artifacts, etc.


(john.b.hurst) #8

Warning: I’m not a guru.

I’m interested to hear what the experts say about this, but I would have hoped you could do this without custom configurations. Are not the “compile” and “runtime” configurations enough?

E.g. suppose you have a client project that wants to use your classifier module.

Then

dependencies {

compile “myorg:classifier-api:1.0”

runtime “myorg:classifier-impl:1.0” }

This assumes there is no static dependency on an implementation class in your client project. For example, instances might be created by Spring (using the impl class name as a String).

My understanding of custom configurations is that they are more for when you need to set up a special classpath. For example, defining a classpath for an Ant task within your Gradle build.

I have probably just exposed my ignorance … let’s see what the experts say.

Regards

John Hurst Wellington, New Zealand


(Etienne Studer) #9

I figured it out, I think. I need to add my own source sets. I will post a solution when I’m done.


(Luke Daley) #10

I just added a sample to our source tree that shows one way to do this:

https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/java/apiAndImpl

However, none of the current IDEs can handle this without a lot of manual tweaking. That is, you can’t use the current Gradle IDEA or Eclipse integration with projects like this. If you want IDE support, your only real option is to split into multiple projects.

To my taste, this would be the ideal way to structure a project that is logically one thing but you do want to separate api from impl. That said, I’d probably split into multiple projects just for the IDE support.

You’ll also notice there is quite a lot of wiring in there. In the future we’ll have a DSL to express this kind of thing which would reduce all of this to just a few lines.