Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DDD: where should logic go that tests the existence of an entity?

I am in the process of refactoring an application and am trying to figure out where certain logic should fit. For example, during the registration process I have to check if a user exists based upon their email address. As this requires testing if the user exists in the database it seems as if this logic should not be tied to the model as its existence is dictated by it being in the database.

However, I will have a method on the repository responsible for fetching the user by email, etc. This handles the part about retrieval of the user if they exist. From a use case perspective, registration seems to be a use case scenario and accordingly it seems there should be a UserService (application service) with a register method that would call the repository method and perform if then logic to determine if the user entity returned was null or not.

Am I on the right track with this approach, in terms of DDD? Am I viewing this scenario the wrong way and if so, how should I revise my thinking about this?

This link was provided as a possible solution, Where to check user email does not already exits?. It does help but it does not seem to close the loop on the issue. The thing I seem to be missing from this article would be who would be responsible for calling the CreateUserService, an application service or a method on the aggregate root where the CreateUserService object would be injected into the method along with any other relevant parameters?

If the answer is the application service that seems like you are loosing some encapsulation by taking the domain service out of the domain layer. On the other hand, going the other way would mean having to inject the repository into the domain service. Which of those two options would be preferable and more in line with DDD?

like image 414
user1790300 Avatar asked Jan 11 '18 05:01

user1790300


People also ask

Where does business logic go in DDD?

All invariant to use-cases logic (business entities, business workflow components, e.g. Domain model, Domain services) goes to the Domain layer (Domain logic). This layer is responsible for concepts of the business domain and business rules.

What is DDD principle?

Domain-Driven Design(DDD) is a collection of principles and patterns that help developers craft elegant object systems. Properly applied it can lead to software abstractions called domain models. These models encapsulate complex business logic, closing the gap between business reality and code.

What is DDD pattern?

DDD patterns help you understand the complexity in the domain. For the domain model for each Bounded Context, you identify and define the entities, value objects, and aggregates that model your domain. You build and refine a domain model that is contained within a boundary that defines your context.

What is invariants in DDD?

In DDD, validation rules can be thought as invariants. The main responsibility of an aggregate is to enforce invariants across state changes for all the entities within that aggregate. Domain entities should always be valid entities. There are a certain number of invariants for an object that should always be true.


2 Answers

I think the best fit for that behaviour is a Domain Service. DS could access to persistence so you can check for existence or uniquenes. Check this blog entry for more info.

I.e:

public class TransferManager
    {
        private readonly IEventStore _store;
        private readonly IDomainServices _svc;
        private readonly IDomainQueries _query;
        private readonly ICommandResultMediator _result;

        public TransferManager(IEventStore store, IDomainServices svc,IDomainQueries query,ICommandResultMediator result)
        {
            _store = store;
            _svc = svc;
            _query = query;
            _result = result;
        }

        public void Execute(TransferMoney cmd)
        {
            //interacting with the Infrastructure
            var accFrom = _query.GetAccountNumber(cmd.AccountFrom);

            //Setup value objects
            var debit=new Debit(cmd.Amount,accFrom);

            //invoking Domain Services
            var balance = _svc.CalculateAccountBalance(accFrom);
            if (!_svc.CanAccountBeDebitted(balance, debit))
            {
                //return some error message using a mediator
                //this approach works well inside monoliths where everything happens in the same process 
                _result.AddResult(cmd.Id, new CommandResult());
                return;
            }

            //using the Aggregate and getting the business state change expressed as an event
            var evnt = Transfer.Create(/* args */);

            //storing the event
            _store.Append(evnt);

            //publish event if you want
        }
    }

from http://blog.sapiensworks.com/post/2016/08/19/DDD-Application-Services-Explained

like image 196
jlvaquero Avatar answered Oct 17 '22 04:10

jlvaquero


The problem that you are facing is called Set based validation. There are a lot of articles describing the possible solutions. I will give here an extract from one of them (the context is CQRS but it can be applied to some degree to any DDD architecture):

1. Locking, Transactions and Database Constraints

Locking, transactions and database constraints are tried and tested tools for maintaining data integrity, but they come at a cost. Often the code/system is difficult to scale and can be complex to write and maintain. But they have the advantage of being well understood with plenty of examples to learn from. By implication, this approach is generally done using CRUD based operations. If you want to maintain the use of event sourcing then you can try a hybrid approach.

2. Hybrid Locking Field

You can adopt a locking field approach. Create a registry or lookup table in a standard database with a unique constraint. If you are unable to insert the row then you should abandon the command. Reserve the address before issuing the command. For these sort of operations, it is best to use a data store that isn’t eventually consistent and can guarantee the constraint (uniqueness in this case). Additional complexity is a clear downside of this approach, but less obvious is the problem of knowing when the operation is complete. Read side updates are often carried out in a different thread or process or even machine to the command and there could be many different operations happening.

3. Rely on the Eventually Consistent Read Model

To some this sounds like an oxymoron, however, it is a rather neat idea. Inconsistent things happen in systems all the time. Event sourcing allows you to handle these inconsistencies. Rather than throwing an exception and losing someone’s work all in the name of data consistency. Simply record the event and fix it later.

As an aside, how do you know a consistent database is consistent? It keeps no record of the failed operations users have tried to carry out. If I try to update a row in a table that has been updated since I read from it, then the chances are I’m going to lose that data. This gives the DBA an illusion of data consistency, but try to explain that to the exasperated user!

Accepting these things happen, and allowing the business to recover, can bring real competitive advantage. First, you can make the deliberate assumption these issues won’t occur, allowing you to deliver the system quicker/cheaper. Only if they do occur and only if it is of business value do you add features to compensate for the problem.

4. Re-examine the Domain Model

Let’s take a simplistic example to illustrate how a change in perspective may be all you need to resolve the issue. Essentially we have a problem checking for uniqueness or cardinality across aggregate roots because consistency is only enforced with the aggregate. An example could be a goalkeeper in a football team. A goalkeeper is a player. You can only have 1 goalkeeper per team on the pitch at any one time. A data-driven approach may have an ‘IsGoalKeeper’ flag on the player. If the goalkeeper is sent off and an outfield player goes in the goal, then you would need to remove the goalkeeper flag from the goalkeeper and add it to one of the outfield players. You would need constraints in place to ensure that assistant managers didn’t accidentally assign a different player resulting in 2 goalkeepers. In this scenario, we could model the IsGoalKeeper property on the Team, OutFieldPlayers or Game aggregate. This way, maintaining the cardinality becomes trivial.

like image 42
Constantin Galbenu Avatar answered Oct 17 '22 05:10

Constantin Galbenu