I'm working on a project at the moment and I'm using this project to try and apply DDD. However, there are parts of my implementation that I just can't get my head around or don't feel right. I'll try to describe how I currently have my application setup;
In my domain layer, I have my aggregates, value objects and a service class for each bounded context. As an example, I have a bounded context 'Account' with the following;
Account - account aggregate root
Profile - value object
EmailAddress - value object
AccountValidator - validates an accounts state
ProfileValidator - validates a profiles state
AccountRepository - interface describing the methods available on the repository
AccountService - at the minute this has a single method createNewAccount(), it accepts a number of primitive values and creates the account and returns it. Before creating the account it first checks to make sure the selected username doesn't already exist. If the username exists, it throws an Exception. (Should this rule be in the domain service?)
In my application layer I have;
AccountRepositoryImp - an implementation of the accountRepository in the domain class, this is a standard Spring Data interface that implements accountRepository.
AccountFacade - basically, another account service that provides methods such as findById(), findByUsername(), createNewAccount(), sendVerificationEmail(), verifyAccount.
AccountController - standard Spring controller that handles all account related actions
I have a number of questions/issues with my approach;
- I have two service classes for each bounded content, one for the domain and one for the application, and while I understand the domain service class should be used only to apply behaviour or business rules that can't be done on the aggregate itself (i.e. need db access), I'm struggling to determine what's an application rule and what's a business rule. For example, an account cannot be created if the username is already in use, I've put this in my domain service, but I'm starting to wonder if it should belong in the createNewAccount() method in the application service. Another rule, "An account can only be deleted by an admin or the account hold themselves", is this application or business?
- As I add more and more methods to my application service, it's going to grow pretty big. This leads me to thinking about implementing CQRS and having a class/method for each command/query. This breaks things down and makes it easier to manage, but I can't help but feel this will bring a lot of duplication to it. For example, In the AccountRepository I can retrieve an account by it's username, and if the account doesn't exist it returns null. Rather than returning null, I wrote a getByUsername() method in the application service so that it throws an exception if the account doesn't exist, rather than a null. This means I can use this method from other methods and I don't need to duplicate throwing the exception. I feel like if I use CQRS, I'm still going to have to keep the application service so it can hold methods that are used by multiple commands/queries to reduce duplicate. I'll end up with yet another layer controller -> command/query -> applicationservice -> domainservice it feels like I'm making the application more complicated than it needs to be
- If I use CQRS, is the command/query or controller responsible for building ViewModels? I'm inclined to thing the command/query can build them, as they'll be use case based and can be re-used if I was to add REST endpoints to the system in the future. If I allowed the controller to make multiple command/queries and build the ViewModel, then if I implement REST at a later date, I'll have to duplicate that code.
Rules - as mentioned above I'm struggling to assign rules to the application or domain. I'll list a few rules below and if I could have some pointer on where they might belong, it would be useful to train my brain;
- An account cannot be created without an email, username, firstname and lastname (I've put this on the domain)
- An account cannot be created if the username is in use (I've put it on the domain)
- A user cannot comment on a project if they are not a project member.
- A comment cannot be left on a closed project
- Only the project owner can edit/manage/administer the project
I do apologise if this question is a little unstructured and my thoughts do appear a bit random. I've been so used to using an anemic model that I've got so many thoughts in my head that I can't structure (hence the question).
AccountFacade
seems to naturally fall under the purview ofAccountRepository
orAccountService
. The latter should just be moved into your Application Layer. Extra layers of indirection only help muddy the waters. Similarly,AccountRepository
should just throw an exception is anAccount
is not found. Why returnnull
? So you can check if the return value isnull
and throw an exception? Get rid of all the middlemen!Validator
classes as they are almost always a sign of an anemic model. In DDD, your entities should validation themselves. Separating data (Account
) from behavior (AccountValidator
) is fundamentally procedural in nature, not OOP, and certainly not DDD.