views:

139

answers:

2

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?

+1  A: 

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.

tvanfosson
Regarding (1), i think my Application Layer could serve the purpose of a Service Layer, if i had another layer i think i might end with layers with no meaning or responsabilities being shared among layers.When you talk about the complexity of the design, i completely agree with you. I've been using an 'Active Records Pattern' approach for most of my applications, and that pattern may seem to be more suited to solve this example.I purposely left this example simplistic, but i'm trying to learn how to model correctly using a Repository Pattern, thats why i'm leaning away from the AR pattern.
Tag
Regarding (2), your suggestion makes perfect sense to me and i think it is indeed the best option in this case.
Tag
A: 

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.

Arnis L.