In my previous post I defined the requirements of a user management microservice and designed the initial domain model of it. Getting lots of positive energy from the community and many valuable comments on Reddit ensured me, that it’s worth going on with the project. In this second part, I’ll detail how the domain model got implemented and what decisions were made behind the code.
In the first part, I mentioned that Domain-Driven Design principles will be used, which implies that the model cannot depend on any framework or infrastructure class. I had created applications so many times with cluttering the domain model with framework specific annotations (eg. JPA or Hibernate), that it felt completely alien to work with bare Java POJO’s again. The only library the domain model uses is Lombok in order to reduce the verbosity normal getters and setters would have caused.
When designing a model with DDD, the first step is the characterization of classes. In Eric Evan’s book Part II. focuses on the Building Blocks of Model-Driven Design . With that in mind, we classify our model classes into the following categories.
Entities have got a clear identify and lifecycle which needs to be managed. From that perspective a User is certainly an entity.
ConfirmationToken [4] however is a borderline case, because logically it doesn’t exist without the context of a user, on the other hand, it can be identified by the token’s value and it has got its own lifecycle.
The same approach can be applied to Session, which could also be a value object, due to its immutable nature, yet it still has an identity and a lifecycle (sessions expire).
In contrast to entities, value objects don’t have a clear identity, that is, they are just grouping a set of attributes and if these attributes are the same as the attributes of another value object of the same type, then we can treat them the same.
When designing the domain model, value objects provided a convenient way to describe set of attributes, which carry a certain piece of information. AddressData, AuditData, ContactData and Password became value objects for this reason.
Although it would have been impractical to implement all of them immutable, as some attributes of them may be changed changed individually, Password was a good candidate for that. When we create an instance of Password, it is created only once with its salt and hash. Upon changing the password, an entirely new instance is created with a new salt and hash.
Aggregates represent a set of objects which are bound together and accessed through a so called root aggregate.
We’ve got two aggregates here user and session. The former contains all the entities and value objects related to users and latter contains only a single entity Session.
Obviously, the aggregate root of user is the User entity. Through an instance of a User entity we manage confirmation tokens, user events and the user’s password.
Aggregate Session became a standalone entity – in spite of being tied to a user’s context – partly due to its disposable nature and partly because we don’t know who the user is when we look a session up. Sessions are created once and they either expire or they get removed on demand.
Domain events are emitted when such an event occurs which needs to be handled by another component of the system.
The user management app has got a single domain event, which is UserEvent and it may refer to one of the following events.
Services carry the business logic which operate on a set of domain model’s classes. In this application UserService manages the life-cycle of Users and also emits the appropriate UserEvents. SessionService is for creating and destroying user sessions.
Repositories are meant to represent a conceptual collection of objects of a entity, however sometimes they’re just used as data access objects. There are two approaches for implementing repositories. One way is to list all the possible data access methods in an abstract repository class or super-interface, as Spring Data does for example, or to create specialized repository interfaces for the task.
For the user management app, I went for the second approach. UserRepository and SessionRepository list only those methods which are absolutely necessary to deal with their entities.
You might have already noticed, that there’s a GitHub repo springuni-particles which contains the some pieces of the user management app, but it doesn’t contain an executable version of the application itself.
The reason, why I wouldn’t provide a single repo with just dropping Spring Boot in with a few @Enable* annotations, is reusability. Most of the projects I came across seamed to be modular at a first glance, however their are just large monoliths without a clear separation of concerns. When you tried to reuse a module of a such a project, you quickly realize, that it depends on many other modules and/or too many external libraries.
springuni-particles (it could have been also called springuni-modules) provides only reusable pieces of modules for certain well defined functionalities. User and session management are examples for that.
springuni-auth-model contains all the domain model classes and that business logic which is needed to manage users’ lifecycle and it’s completely framework agnostic. Its repositories and can be implemented by using any data storage mechanism which is the best fit for the actual task at hand. As well as, PasswordChecker and PasswordEncryptor can be used to implement any strong password hashing technique.
springuni-commons is just the dumping ground of common utilities. It’s tempting to pull well-known 3rd party libs (eg. Apache Commons Lang, Guava, etc.) in, which extend the standard library of the JDK. On the other hand, I found myself many times just using only a few classes of these very extensive libraries. I particularly like Apache Commons Lang’s StringUtils and Apache Common Collection’s CollectionUtils classes, however, I would rather provide a highly specialized StringUtils and CollectionUtils classes for the current project instead of adding external dependencies.
sprinuni-crm-model is here to define the common value objects for handling contact data, like address, country etc. Although the advocates of microservice architecture would vote against using shared libraries, yet I think that this particular point might need to revised from time-to-time for the task at hand. I was involved in a few CRM integration projects recently and having to re-implement the domain model for almost the same thing at various bounded contexts (ie. User, Customer, Contact) over and over again was tedious. That said, I think that’s worth having a small common library for the domain model of contact data.
Building a user management microservice (Part 3): Implementing and testing repositories
If you like Java and Spring as much as I do, sign up for my newsletter.