I consider a module to be a kinda “first-class citizen” in my code ever since I’ve read the DDD book. Probably finding the module boundaries is the second most important thing after finding service boundaries.
The most important parts are highlighted in the book, here they are:
Choose MODULES that tell the story of the system and contain a cohesive set of concepts. This often yields low coupling between MODULES , but if it doesn’t, look for a way to change the model to disentangle the concepts, or search for an overlooked concept that might be the basis of a MODULE that would bring the elements together in a meaningful way. Seek low coupling in the sense of concepts that can be understood and reasoned about independently of each other. Refine the model until it partitions according to highlevel domain concepts and the corresponding code is decoupled as well.
Unless there is a real intention to distribute code on different servers, keep all the code that implements a single conceptual object in the same MODULE , if not the same object.
Well, no wonder that what is written there is just “Your code should be loosely coupled and highly cohesive”. What the book lacks is an example of how to define a module. The description of wrong approaches would be great too, but there is none.
But recently I stumbled upon a paper by David Parnas called “On the Criteria To Be Used in Decomposing Systems into Modules”. Well, the name is pretty descriptive, so no need to elaborate on what it is about. There is an example (which I didn’t fully comprehend), but what is more important there is an example of wrong (and most popular) approach when identifying module boundaries: using steps your software must go through as modules. It totally contradicts the OOP approach and is an example of procedural thinking.
So here is a quick example. Say your system is involved in online transaction processing. So your (procedural) thinking might go like this:
So it very likely that your modules are:
So, why it is procedural? Because the knowledge of the concepts used by business experts is spread all over the codebase. Validator knows what data is acceptable. Business rules prevent you from sending a request that shouldn’t have been sent. Business logic module accepts the data received from payment system and simply performs a database insert or update — hence the quotes in module name. So conceptually these modules are nothing but a set of procedures that handle input data and pass it further. Very often the names of these procedures end with “-er” or “-or” since they do something with the input data, but have to pretend to be a noun: Validator, PaymentApplicationIsExpiredChecker, PaymentRequestSender, TransactionSaver.
What concepts do we have? Apparently, we have a payment application and payment transaction. You can identify some more if you follow this example: they are customer notification, recurring registration, saved user card, etc. Entities representing these concepts know what data is appropriate for them and are aware of conditions they can operate under. They know what business logic they need to carry out and when. Well, actually they are business logic. I think this inherent independence of domain concepts is what makes modules based on this notion loosely coupled.
That’s what Parnas has to say about his way of module identification:
We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.
The concepts themselves are not likely to change. But their implementation is.
[…] it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart.
— because it’s intrinsically procedural.
It’s really very important: module is not a technical capability of your programming language. It is a higher-level description of your system carved into your codebase. Once again, OOP proves to be a manifestation of business-IT alignment.