Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In a DDD approach, is this example modelled correctly?

Just created an acc on SO to ask this :)

Assuming this simplified example: building a web application to manage projects...
The application has the following requirements/rules.

1) Users should be able to create projects inserting the project name.
2) Project names cannot be empty.
3) Two projects can't have the same name.

I'm using a 4-layered architecture (User Interface, Application, Domain, Infrastructure).
On my Application Layer i have the following ProjectService.cs class:

public class ProjectService
{
    private IProjectRepository ProjectRepo { get; set; }

    public ProjectService(IProjectRepository projectRepo)
    {
        ProjectRepo = projectRepo;
    }

    public void CreateNewProject(string name)
    {
        IList<Project> projects = ProjectRepo.GetProjectsByName(name);
        if (projects.Count > 0) throw new Exception("Project name already exists.");

        Project project = new Project(name);
        ProjectRepo.InsertProject(project);
    }
}

On my Domain Layer, i have the Project.cs class and the IProjectRepository.cs interface:

public class Project
{
    public int ProjectID { get; private set; }
    public string Name { get; private set; }

    public Project(string name)
    {
        ValidateName(name);
        Name = name;
    }

    private void ValidateName(string name)
    {
        if (name == null || name.Equals(string.Empty))
        {
            throw new Exception("Project name cannot be empty or null.");
        }
    }
}




public interface IProjectRepository
{
    void InsertProject(Project project);
    IList<Project> GetProjectsByName(string projectName);
}

On my Infrastructure layer, i have the implementation of IProjectRepository which does the actual querying (the code is irrelevant).


I don't like two things about this design:

1) I've read that the repository interfaces should be a part of the domain but the implementations should not. That makes no sense to me since i think the domain shouldn't call the repository methods (persistence ignorance), that should be a responsability of the services in the application layer. (Something tells me i'm terribly wrong.)

2) The process of creating a new project involves two validations (not null and not duplicate). In my design above, those two validations are scattered in two different places making it harder (imho) to see whats going on.

So, my question is, from a DDD perspective, is this modelled correctly or would you do it in a different way?

like image 922
Tag Avatar asked Apr 03 '10 12:04

Tag


People also ask

What is model in DDD?

The Domain Model is your organised and structured knowledge of the problem. The Domain Model should represent the vocabulary and key concepts of the problem domain and it should identify the relationships among all of the entities within the scope of the domain.

Is DDD a design pattern?

Domain-driven design (DDD) is a major software design approach, focusing on modelling software to match a domain according to input from that domain's experts. Under domain-driven design, the structure and language of software code (class names, class methods, class variables) should match the business domain.

What makes using DDD an effective architectural approach?

When we are using DDD, we can use different architectures. Three-tier architecture or onion architecture for example. DDD is all about the domain, not layers. The layers exist to help developers manage the complexity in the code, it's not related to the deployment of the service.

Which approach we can use for domain-driven design?

Domain-Driven Design is a concept introduced by a programmer Eric Evans in 2004 in his book Domain-Driven Design: Tackling Complexity in Heart of Software. It is an approach for architecting software design by looking at software in top-down approach.


2 Answers

The process of creating a new project involves two validations (not null and not duplicate). In my design above, those two validations are scattered in two different places making it harder (imho) to see whats going on.

Project can't and shouldn't be aware of all projects in application (item itself shouldn't be aware of all the other items in list), therefore - it's responsibility of domain service (instead of application service. check Evans book to understand exact difference).

There are many kinds of validation. And there can't be universal validation mechanism. DDD just says that you must put domain validation in domain model.

like image 85
Arnis Lapsa Avatar answered Sep 19 '22 20:09

Arnis Lapsa


I think part of the confusion with (1) is that you're missing a layer -- insert a service layer in your architecture and your problem goes away like magic. You can put the service and the repository implementation in the service layer -- i.e., you have a service that uses a concrete implementation of the repository. Other services are free to choose an alternative implementation of the repository if they want. Your application is free to choose whatever service interface that it likes. Having said that, I'm not sure that it really matters in most cases. In nearly all of my applications I've got one "domain/datalayer" that basically fixed. I might layer a repository on it or not depending on how complicated the business logic is. Same with the service -- it may simply not be necessary if the project isn't very complicated. If it becomes so later, I can always refactor. Typically I'd put my repository in the same project as my data context (using LINQ) and, if there were a service, it would be in a separate project (because typically it would be exposed as a web service as well).

With regard to (2) you need to think about the problem from a concurrency perspective. Your check for a duplicate name is best handled by a database constraint if possible. I think this is the easiest way to enforce this logic. You can certainly check if there is a duplicate before attempting an insert, but unless you do it inside a transaction you can't guarantee that another process won't come along and insert one between your check and your insert. The database constraint solves this problem. Moving the check into the insert logic (same transaction) also solves the problem, but regardless I think you need to be prepared to handle it as an insert failure as well as (or instead of) a validation error.

like image 36
tvanfosson Avatar answered Sep 18 '22 20:09

tvanfosson