Software Engineering
architecture libraries code-reuse semantic-versioning
Updated Mon, 20 Jun 2022 23:08:27 GMT

Best practices on sharing code between open source libraries


I have an Android library used by a decent amount of people, let's call it library A.

I am building a second library, library B.

The two libraries are conceptually related, they solve different problems in the same domain. It's likely that the two libraries will be used together, but it's not mandatory.

The problem: library A contains some classes and interfaces I'd like to reuse in library B.

Are there any best practices on how to do this? This is the first time I encounter this problem and, being the libraries used by other people, I'd like to implement the solution that makes more sense.

I can see three approaches:

  1. Copy-paste interfaces and classes I want to reuse, from library A to library B.
  2. Import library A in library B.
  3. Take out of library A the interfaces and classes I want to reuse, publish them under a new CORE library and import the CORE library in both library A and library B.

Are there more possible approaches I am not considering?

Problems I can see with this solutions:

  1. This is the worse solution. It doesn't follow the DRY principle and, most importantly, a user of both libraries would end up having interfaces like: com.libraryA.Interface1 and com.libraryB.Interface1. This doesn't allow for nice interaction between library A and B.
  2. Library B would import a bunch of code that it doesn't need. Most importantly, users of library B would always have to import library A, because of the shared interfaces from A to B.
  3. This seems the most elegant solution, but how should I handle versioning in this situation? Will library A, B and CORE always be required to have the same version number? If not, would it make sense to have different version numbers for A, B and CORE? Changes on CORE would affect both A and B and require a version bump on all three. Changes on A and B would be isolated.

Are there any more problems I am not seeing?




Solution

Whether your third option is the correct one depends on the situation. The basic question is whether A currently contains two things that really are reasonably separate and each is useful as a distinct entity.

For example, if you break A apart into two pieces, and have a core with both A and B as clients, is it likely that the core would also do a good job of supporting C, D and E?

Or, with A broken into Core and A-prime, would you end up with a single thing that's basically broken down the middle, and most likely a C, D or E would still need to use pieces of both the Core and A-prime?

Once you've determined that, the conclusion probably becomes pretty obvious: if A really does represent two separate things that happen to (currently) live in the same library, and separating them gives a Core that's likely to be more cleanly usable on its own, then go ahead and separate them.

On the other hand, if it just happens that almost any client of A is likely to use 80% (or whatever) of what it provides, but each will use a different 80%, then it makes more sense to leave it as a whole.

As for versioning: whether you leave A as a single piece or break it into two, you have pretty much the same problem: one library with (at least) one other that depends on it.

One fairly common strategy is to break versioning into two categories: minor changes are things like bug-fixes, but for a client that depends only on documented behavior, a newer version of the library should act as a substitute for any older version. This means you can fix bugs and add features, but not make any material changes to documented behavior.

If you make a change to documented behavior, that would be a major version change. It requires that a client be aware of the change.

So, whether you break A in half or not, you have B depending on something else. For minor versions, some client might depend on having 1.1 or newer, and another client might depend on having 1.2 or newer (but in both cases, the "or newer" only includes 1.x, not some possible 2.x).

Having the same version numbers for all of them is mostly an advantage when you have a number of libraries that clearly are separate, but are (for whatever reason) used together sufficiently often that it's easier for a user to keep track of them together, so if s/he's using both A and B, they don't have to keep track of three separate version numbers.





Comments (3)

  • +0 – "The basic question is whether A currently contains two things that really are reasonably separate and each is useful as a distinct entity." Core would offer no functionalities to the users, it would only be used as a base to build stuff on top of it. That's why can't make up my mind about it, it's separate, but useless by it self. Does it make sense to force users to import the Core library only to be able to use library A? (because of the shared interfaces it contains) — Jun 17, 2018 at 18:50  
  • +1 – If the Core can't reasonably be used separately from A, then it shouldn't exist separately from A. — Jun 17, 2018 at 18:55  
  • +0 – I've edited the question. Solution 2. has one significant problem I had overlooked. — Jun 17, 2018 at 19:14