Modules, Cycles, Unwanted Friends - The Modularity Challenges In Enterprise Projects
Building modules and components is not that hard. You "only" have to encapsulate the internal component implementation and expose a clean and easy to use interface. ...at least on paper. In practice you will be confronted with the following challenges in the early iterations:
- The external interface is too coarse and far less interesting for internal reuse, than you had thought.
- The interesting things are residing inside the component. They are, however, well encapsulated and not accessible from the outside.
address / geo-locationservice. The module
customermgmtexposes CRUD services and the address component extensive search capabilities. So far the world is perfect.
Now: a customer has an address - how to model that? The external, customer contract will have to reference the address somehow. That is often modeled as a direct relation between DTOs (just a getter). The external view of the customer component is now dependent on the address component. The implementation is still independent. The relation between the customer and the address has to be persisted somehow. And now the trouble starts. Now the implementation of the customer component is dependent on the address component - because of direct (JPA) link between both modules. Now the internal implementation *and* the component contract are dependent on each other. You are using JPA 1.0 - and your database experts just don't want to introduce an additional mapping table between the customer and the address. So you have to model a bi-directional relation between the customer entity and the address (introducing a back-link with a mappedBy attribute). Now you get a bidirectional dependency between the implementation of your component - the external dependency of the customer can remain unidirectionally dependent on the address. This is only true if you are using DTOs. So you get two components which should be independent of each others, but are actually tightly coupled. Your modules have to expose everything - if you are using Java EE 6 - the DTOs and JPA-entities are dependent of each other - probably only the very thin boundary may remain independent. In practice you will get e.g. an
invoicemodule in addition, which will be dependent on both the customer and the address.... You can do the following to "improve" the situation:
- Factor out all entities into a common package. Often called "domain", "model" or even "common". Such a common package is not cohesive (it contains multiple business concepts) and also not very good to maintain (the generic names have nothing to do with the actual business). This approach looks great ...on paper.
- Drop JPA-relations and introduce proxy-objects, which contain the ID and can be resolved on demand. This will significantly increase the amount of code and will hit your performance. You will be not able to use joins...
- Allow bidirectional friend-dependencies between modules. In that case it will be hard to introduce a framework like OSGi, jigsaw or something else. But you can still put all "business components" into few modules. Then the real benefit of OSGi, Jigsaw etc is questionable.
- Remove OR-mappers and go with "plain" JDBC. Let the DB handle the dependencies for you. In most cases this is not really a maintainable option.
So, you shouldn't kill any OSGi project - you should implement some typical use cases (PoCs) with modularity solution of your choice before the project really starts. This is actually independent of any framework like OSGi, jigsaw or EMS (esoteric module system) :-).
[See also "Real World Java EE Patterns, Rethinking Best Practices" book, Page 267, (Monolithic or Loosely Coupled?) for more in-depth discussion]
In a project I'm working on we had a problem with some domain entities depending on User object (which resided in a separate module, exposing just a DTO). The solution we came to was to map the UserDTO to the same database table as User entity and use this DTO in entity classes in other components, defining relationship as if it were a regular entity (we don't allow cascading saves/updates to the UserDTO fields).
The drawback is that we can't let JPA generate the DB schema, as DTO are read-only and have insertable="false" on it's columns (not a big pain for us though, as we have other DTOs mapped to database views anyway, aggregating data from different domain entities in one transfer object).
Works great for now, we'll see what happens when the project grows bigger.
Posted by Marcin Deryło on March 11, 2010 at 03:24 PM CET #
exactly - in another project we used materialized views for similar purposes. Typical business "enterprise" projects are lot harder to partition, than the technical stuff. ...and no framework can help us here :-),
thanks for your thoughts!,
Posted by Adam Bien on March 11, 2010 at 03:39 PM CET #
Why would you separate customer from his address?
What would you do with independent address component?
I think customermgmt component should contain customer's address similiary as it contains customer's name, age, etc.
Posted by Wojciech Gdela on March 12, 2010 at 04:02 PM CET #
it is just an example. You get the same problem with orders, invoices and e.g. statistics.
If you are not careful, you will end with a huge package with too many, not-cohesive entities...
Posted by Adam Bien on March 12, 2010 at 07:47 PM CET #
Sorry, but I disagree. Let me explain: Software Architecture is about building components. Components make it easier to reason about the system: You can reason about each component in isolation.
If you create a dependency from customer to address a change to the address component will influence the customer component. But you can still change the customer component *without* considering the address component - because there is no dependency from address to customer. As soon as you introduce a dependency from address to customer the two components are in fact not separated any more - a change to one of the two components can influence the other one and therefore you cannot reason about them in isolation any more. So while you still talk about two "components" in fact they are one.
In your blog post I actually don't understand why a dependency from address to customer must be introduced. IMHO the customer component should be responsible for finding the address for a customer. I don't see why the address component should depend on the customer.
Now in this specific example there is just one dependency in the wrong direction - which is not too bad. But in real life I have seen quite a few systems with as many dependencies in one direction as in the other - these systems obviously violate the separation of the components. Therefore managing dependencies is very important for architecture and architects.
Posted by Eberhard Wolff on November 06, 2011 at 06:57 PM CET #
"You can reason about each component in isolation."
Exactly: and this is almost impossible for business components with JPA entities, but relatively easily for services and technical components.
"In your blog post I actually don't understand why a dependency from address to customer must be introduced..."
It is hard to find a trivial, but meaningful (and customer-independent :-)), example. In real world such challenges are not that easy to resolve.
"Therefore managing dependencies is very important for architecture and architects."
+1. But this is a trade off as well. I see increasingly architects using (great) tools like SonarJ or Structure101 managing the dependencies by moving the items until the dependency graph looks gorgeous. The problem: it is no more impossible to name the packages (namespaces) properly. Usually meaningless names like: domain, model, entities, infrastructure, base, core etc. are used, because their contain unrelated (from business perspective), but dependent (from technical perspective) components.
Thanks for your constructive comment!,
Posted by Adam Bien on November 13, 2011 at 07:15 AM CET #