Maven module layout for DDD projects
How do you layout your Maven modules when doing DDD projects? Do you fit all layers (presentation, application, domain, infrastructure) in a single module, or do you create a multi-module layout with a separate module for each layer? Or something else entirely?
I notice that the DDD Samp开发者_运维知识库le App, developed by the companies Domain Language and Citerus, uses a single Maven module, with each layer as a separate Java package inside that module. Is this the established best practice, or should I consider a more granular module layout?
Generally Module separation and packaging is a question of deployment and development practicalities. Who else is going to require the code? If I want to change functionality Y, is it all in package X?
Caveat: the sample app is packaged as single app to make it easy to consume as a learning tool. But here are some reccomendations using it as a example and pretending it's real. I will make some assumptions about it in a vacuum for illustrative purposes, but a responsible practitioner of DDD would grab a Domain Expert and interview the development team to validate any and all assumptions about the domain, context boundaries and the relationship between those contexts. You can't do DDD alone.
Get the Modeling Right
Step one would be to focus on getting the modeling right and the context boundaries defined. I would not worry about infrastructure layers so much as I would worry about the various Contexts and their models within the domain. The critical distinctions in the Sample App are those between these different contexts, there are three contexts in this app
- Booking
- Routing
- 3rd Party Vendors/Ports/Ships etc
If you notice those are clearly separated by the root java packages
- se.citerus.dddsample
- com.pathfinder
- com.aggregator
The Layers are mostly to facilitate communication between these contexts and to separate infrastructure concerns from the domain work making testing easier and domain responsibilities more plain. Infrastructure is important but the fact the sample app is using XML here and JMS there and hibernate over there are secondary concerns to the domain modeling going on.
The example app makes this separation very clear, it's easy to see where the Aggregates Roots are:
- Cargo
- HandlingEvent
- Location
- Voyage
Breaking up the java packages by Aggegrate Roots is solid practice. A Leg means nothing outside the Cargo aggregate, a schedule means nothing outside of the Voyage aggregate, a HandlingHistory means nothing outside the HandingEvent aggregate. Keeping the domain model isolated from and testable without infrastructure is a good practice. But you'd probably not extend that decoupling to the module level. It would not be a rule to say have all your domain objects in one jar and all your infrastructure in another. Development and version burdens might become painful.
Identify Bounded Contexts
The key is how the various context's models are separate/unshared. In the booking context a route is an Itinerary with a set of Legs. In the routing domain it's a Graph in the real Computer Science sense of the word, so that domain can solve routing problems using well studied graph traversal algorithms right out of your Algorithms class from college.
The two contexts Booking and Routing are in a close Partnership where they both maintain a shared interface between the two models of Edges and Nodes and Itineraries and Legs. This translation is maintained between model is managed in the ExternalRoutingService that's where a TransitPath becomes an Itinerary. Obviously that is a very critical integration point that should be well covered in tests and managed through Continuous Integration
The other context is 3rd party integration to report HandingEvents to the app about cargo status. This is achieved through a pattern called Published Language. In a nutshell we don't care what the cargo models of 3rd parties look like, as long as they report handling events to us following our Publish XML spec HandlingReport.
The relationship between the 3rd party context and the Booking Domain is called Conformist, they submit data following our defined specification we do not alter our models to make this easier for them. The burden is on them to conform to us. That said, that's my guess of the situation, it could be that there's a very important vendor out there and they are in fact defined the XML model not us. Only interviews with the fictional team could really characterize that.
So in summary group all classes related to an Aggregate closely (same package perhaps). Define Context boundaries clearly and ensure there's a clear integration point, define the relationship of the contexts Partnership, Shared Kernel, Published Language, Open Host Service, Conformist, etc.
Based on that in the example we could probably package the various contexts in separate maven modules for Cargo Booking, Route Path Finding and HandlingEvent Aggregation. If that made sense given the practicals of the development methodology and team organization, and only if that was the case.
Look for Modules in Bounded Contexts
Get your context boundaries rights. Define your Aggregates correctly into nice verticals. Reduce coupling to clear well defined interfaces.
Look for modules in your contexts. They are the most natural candidates for separate modules and the separation may help more strictly enforce and document context boundaries. However like so much of software design it's not a hard and fast rule and will really depend on the specific case. I can envision and have seen/wrote applications that have different write models and read models (think normalized and denormalized for say reporting) and contexts for each, that for practical matters might still be packaged in a single module.
Another point be wary of sharing Aggregate Roots between contexts, this is the DDD shared kernel pattern and should be used very sparingly as it can quickly spiral into a large messy domain model that doesn't serve the needs of any context well. Note that the example app does not share models between the RoutingService and the BookingService. Putting all of domain's aggregates roots in a single module might inadvertently encourage this practice.
精彩评论