In this course, we will learn the concepts of microservice and spring framework with a focus on Domain-Driven Design.
- Domain-Driven Design
- Beginner Java developers
- Java developers interested in learning how to Build and Deploy RESTful Web Services
- Java Developers who want to develop web applications using the Spring framework
- Java Developers who want to develop web applications with microservices
- Java Developers who wish to develop Spring Boot Microservices with Spring Cloud
- Basic Java knowledge
Hello there. In the previous video, we talked about aggregates and repositories. In this video, we will talk about factories and modules. If you're ready, let's begin with modules. First, we need to understand what a module is before we can understand how to use it. A module is a group of closely related packages and resources along with the new module descriptor file. The packages inside a module are identical to the Java packages we've been using since the inception of Java. When we create a module, we organize the code internally in packages just like we previously did with any other project. Modules are implemented as namespaces or projects. You use them to organize and encapsulate related concepts, entities, and value objects. So, you can simplify your understanding of larger domain models. Modules are used to decompose the domain model.
Don't confuse them with sub-domains that decompose the domain and bounded context that delimit the applicability of a domain model. Modules enable developers to quickly read and understand the domain model in code before digging deep into class files. They also act as a responsibility boundary, clearly defining parts of the domain model and ensuring that relationships between domain objects are kept to a minimum. The Java Platform Module System (JPMS) encourages the building of a more reliable and strongly encapsulated modules. As a result, these features can help isolate our context and establish clear boundaries. So, why do we feel the need to use modules? For a large and complex application, the model tends to grow bigger and bigger.
The model reaches a point where it is hard to talk about as a whole and understanding the relationships and interactions between different parts becomes difficult. For that reason, it is necessary to organize the model into modules. Modules are used as a method of organizing related concepts and tasks in order to reduce complexity. Modules are widely used in most projects. It's easier to get the picture of a large model if you look at the modules it contains and then at the relationships between those modules. After the interaction between modules is understood, one can start figuring out the details inside a module. It's a simple and efficient way to manage complexity. Another reason for using modules is related to code quality. It is widely accepted that software code should have a high level of cohesion and a low level of coupling.
While cohesion starts at the class and method level, it can be applied at the module level. It is recommended to group highly related classes into modules to provide the maximum cohesion possible. There are several types of cohesion. Two of the most common are communicational cohesion and functional cohesion. Communicational cohesion is achieved when parts of the module operate on the same data. It makes sense to group them because there is a strong relationship between them. Functional cohesion is achieved when all parts of the module work together to perform a well-defined task. This is considered the best type of cohesion. Using modules in design is a way to increase cohesion and decrease coupling.
Modules should be made up of elements that functionally or logically belong together assuring cohesion. Modules should have well-defined interfaces that are accessed by other modules. Instead of calling three objects of a module, it's better to access one interface because it reduces coupling. Low coupling reduces complexity and increases maintainability. It's easier to understand how a system functions when there are a few connections between modules that perform well-defined tasks, then when every module has lots of connections to all the other modules. Okay, now let's talk about the factories. Entities and aggregates can often be large and complex, too complex to create in the constructor of the root entity. In fact, trying to construct a complex aggregate in its constructor is in contradiction with what often happens in the domain itself where things are created by other things like electronics get created on assembly lines. It's like having the printer build itself.
When a client object wants to create another object, it calls its constructor and possibly passes some parameters. But when object construction is a laborious process, creating the object involves a lot of knowledge about the internal structure of the object, the relationships between the objects contained and the rules applied to them. This means that each client of the object will hold specific knowledge about the object built. This breaks the encapsulation of the domain objects and of the aggregates. If the client belongs to the application layer, a part of the domain layer has been moved outside messing up the entire design. The creation of an object can be a major operation in itself, but complex assembly operations do not fit the responsibility of the created objects. Combining such responsibilities can produce ungainly designs that are hard to understand.
Therefore, a new concept is necessary to be introduced, one that helps to encapsulate the process of complex object creation. This is called a factory. Factories are used to encapsulate the knowledge necessary for object creation and they are especially useful to create aggregates. When the root of the aggregate is created, all the objects contained by the aggregate are created along with it and all the invariants are enforced. It is important for the creation process to be atomic. If it's not, there is a chance for the creation process to be half done for some objects, leaving them in an undefined state. This is even more true for aggregates. When the root is created, it is necessary that all objects subject to invariants be created too. Otherwise, the invariants cannot be enforced. For immutable value objects, it means that all attributes are initialized to their valid state.
If an object cannot be created properly, an exception should be raised, making sure that an invalid value is not returned. Therefore, a factory shifts the responsibility for creating instances of complex objects and aggregates to a separate object, which may itself have no responsibility in the domain model, but it's still part of the domain design. It provides an interface that encapsulates all complex assembly and does not require the client to reference the concrete classes of the objects being instantiated and it creates entire aggregates as a unit enforcing their invariants. All right, I think we've talked enough about factories and modules. Let's take a short break here, and I'll see you in the next video.
OAK Academy is made up of tech experts who have been in the sector for years and years and are deeply rooted in the tech world. They specialize in critical areas like cybersecurity, coding, IT, game development, app monetization, and mobile development.