Kuba T
Kuba T

Reputation: 3083

How to model associations in DDD approach?

I'm learning DDD approach step by step with imaginary business domain by reading books of Eric Evans and Vaughn Vernon and I try to implement it using in my project using PHP (but it really doesn't matter here).

Recently I've been reading a lot of Aggregate, AggregateRoot and Entity patterns for models that should be defined by a domain. And, frankly, I'm not sure I understand all definitions well so I decided to ask my questions here.

At first I'd like to present my (sub)domain responsible for employees' holidays management which should make answers for my questions easier.

The most trivial case is that the Employee can be found in many Teams. When the employee decides to take few days off, he has to send a HolidaysRequest with metadata like type of holidays (like rest holidays, some days off to take care of his child, etc.), the acceptance status and of course time range when he's not going to appear in his office. Of couse HolidaysRequest should be aware of which Employee has sent the HolidaysRequest. I'd like also to find all HolidaysRequest that are sent by Employee.

I'm quite sure that things like DateRange or HolidayType are pure ValueObjects. It's quite clear for me. The problems start when I have to define boundries of entities. I may have bad practices of defining associations by nesting objects in entities, so, please, tell me finding out the definitions of responsibilities here.

  1. What is an entity here? What should be an Aggregate and where's the place for AggregateRoot?
  2. How to define associations between entities? E.g. an Employee can belong to multiple Teams or HolidaysRequest is authored by Employee and assigned to another Employee who can accept it. Should they be implemented as Aggregates?

Why I'm asking these questions? Because few weeks ago I've posted a question here and one of answers was to think about relations between Employee and Teams, that they should be in the single Aggreate called EmployeeInTeam but I'm not sure I understand it in proper way.

Thanks for any advice.

Upvotes: 2

Views: 1140

Answers (1)

Alisson Reinaldo Silva
Alisson Reinaldo Silva

Reputation: 10705

The main thing about DDD, is to put focus in the domain, that's why its called Domain Driven Design.

When you start asking about relationships, aggregates and entities without even deeply exploring what consists your domain, you're actually looking for database modeling instead of domain.

Please, I'm not saying you're asking wrong questions, nor criticising they, I think you're not wrong at all when trying to put in practice while studying.

I'm not DDD expert, I'm learning just like you, but I'm gonna try to help.

Start by thinking what situation's may arise about Holydays Management. When you have different rules for something, you could start by using strategies (I'm saying is the final solution).

Building a nice and meaningful domain, is very hard (at least for me). You write code. Test it. Have insights, throw your code way and rewrite it. Refactor it. In your software's lifecycle, you should put focus on domain, therefore you should be always improving it.

Start by coding (like a domain's draft) to see how it looks like. Let's exercise it. First of all, why do we need to manage this stuff? What problem are we trying to solve? Ahh, sometimes employees ask some days off, we want to control it. We may approve or not, depending on the reason they want "holyday", and how is our team status. If we decline and they still go home, we'll late decide whether we fire or discount in salary. Enforcing ubiquitous language, let's express in code this problem:

public interface IHolydayStrategy
{
    bool CanTakeDaysOff(HolydayRequest request);
}

public class TakeCareOfChildren : IHolydayStrategy
{
    public bool CanTakeDaysOff(HolydayRequest request)
    {
        return IsTotalDaysRequestedUnderLimit(request.Range.TotalDays());
    }

    public bool IsTotalDaysRequestedUnderLimit(int totalDays)
    {
        return totalDays < 3;
    }
}

public class InjuredEmployee : IHolydayStrategy
{
    public bool CanTakeDaysOff(HolydayRequest request)
    {
        return true;
    }
}

public class NeedsToRelax : IHolydayStrategy
{
    public bool CanTakeDaysOff(HolydayRequest request)
    {
        return IsCurrentPercentageOfWorkingEmployeesAcceptable(request.TeamRealSize, request.WorkingEmployees)
            || AreProjectsWithinDeadline(request.Projects);
    }

    private bool AreProjectsWithinDeadline(IEnumerable<Project> projects)
    {
        return !projects.Any(p => p.IsDeadlineExceeded());
    }

    private bool IsCurrentPercentageOfWorkingEmployeesAcceptable(int teamRealSize, int workingEmployees)
    {
        return workingEmployees / teamRealSize > 0.7d;
    }
}

public class Project
{
    public bool IsDeadlineExceeded()
    {
        throw new NotImplementedException();
    }
}

public class DateRange
{
    public DateTime Start { get; set; }
    public DateTime End { get; set; }

    public int TotalDays()
    {
        return End.Subtract(Start).Days;
    }

    public bool IsBetween(DateTime date)
    {
        return date > Start && date < End;
    }
}

public enum HolydayTypes
{
    TakeCareOfChildren,
    NeedToRelax,
    BankOfHours,
    Injured,
    NeedToVisitDoctor,
    WannaVisitDisney
}

public class HolydayRequest
{
    public IEnumerable<Project> Projects { get; internal set; }
    public DateRange Range { get; set; }
    public HolydayTypes Reason { get; set; }
    public int TeamRealSize { get; internal set; }
    public int WorkingEmployees { get; internal set; }
}

Here is how I quickly wrote this:

  • Holydays may be granted or not, depending on the situation and reason, let's create a IHolydayStrategy.
  • Created an empty (propertyless) HolydayRequest class.
  • For each possible reason, let's create a different strategy.
  • If the reason is to take care of children, they can take days off if the total days request is under a limit.
  • If the reason is because the employee has been injured, we have no choice other than allowing the request.
  • If the reason is because they need to relax, we check if we have an acceptable percentage of working employees, or if projects are within deadline.
  • As soon as I needed some data in the strategy, I used CTRL + . to automagically create properties in HolydayRequest.

See how I don't even know how these stuff are going to be stored/mapped? I just wrote code to solve a problem, and get piece of information needed to resolve it.

Obviously this is not the final domain, is just a draft. I might take away this code and rewrite, if needed, no feelings for it yet.

People may think it's useless to create an InjuredEmployee class just to always return true, but the point here is to make use of ubiquitous language, to make things as explicit as possible, anyone would read and understand the same thing: "Well, if we have an injured employee, they are always allowed to take days off, regardless of the team's situation and how many days they need.". One of the problems this concept in DDD solves is the misunderstanding of terms and rules between developers, product owners, domain experts, and other participants.

After this, I would start writing some tests with mock data. I might refactor code.

This "3":

    public bool IsTotalDaysRequestedUnderLimit(int totalDays)
    {
        return totalDays < 3;
    }

and this "0.7d":

    private bool IsCurrentPercentageOfWorkingEmployeesAcceptable(int teamRealSize, int workingEmployees)
    {
        return workingEmployees / teamRealSize > 0.7d;
    }

are specifications, In my point of view, which shouldn't reside in a strategy. We might apply Specification Pattern to make things decoupled.

After we get to a reasonably initial solution with passed tests, now let's think how should we store it. We might use the final defined classes (such as Team, Project, Employee) here to be mapped by an ORM.

As soon as you started writing your domain, relationships will arise between your entities, that's why I usually don't care at all how the ORM will persist my domain, and what is Aggregate at this point.

See how I didn't create an Employee class yet, even though it sounds very important. That's why we shouldn't start by creating entities and their properties, because it's the exact same thing as creating tables and fields.

Your DDD turns into Database Driven Design that way, we don't want this. Of course, eventually we'll make the Employee, but let's take step by step, create only when you need it. Don't try to start modeling everything at once, predicting all entities you're going to need. Put focus on your problem, and how to solve it.

About your questions, what is entity and what is aggregate, I think you're not asking the definition of them, but whether Employee is considered one or other, considering your domain. You'll eventually answer yourself, as soon as your domain start being revealed by your code. You'll know it when you started developing your Application Layer, which should have the responsibility of loading data and delegating to your domain. What data my domain logic expects, from where do I start querying.

I hope I helped someone.

Upvotes: 2

Related Questions