在 DDD 方法中,这个示例建模是否正确?

发布于 2024-08-27 18:22:59 字数 1733 浏览 6 评论 0原文

刚刚创建了一个 acc 来询问这个问题:)

假设这个简化的示例:构建一个 Web 应用程序来管理项目...
该应用程序具有以下要求/规则。

1) 用户应该能够创建插入项目名称的项目。
2) 项目名称不能为空。
3) 两个项目不能重名。

我使用 4 层架构(用户界面、应用程序、域、基础设施)。
在我的应用程序层上,我有以下 ProjectService.cs 类:

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);
    }
}

在我的域层上,我有 Project.cs 类和 IProjectRepository.cs 接口:

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);
}

在我的基础设施层上,我有 IProjectRepository 的实现,它执行实际的查询(代码无关)。


我不喜欢这个设计的两件事:

1)我读到存储库接口应该是域的一部分,但实现不应该是。这对我来说没有意义,因为我认为域不应该调用存储库方法(持久性无知),这应该是应用程序层中服务的责任。 (有件事告诉我我错了。)

2)创建新项目的过程涉及两次验证(不为空且不重复)。在我上面的设计中,这两个验证分散在两个不同的地方,这使得(恕我直言)很难了解发生了什么。

所以,我的问题是,从 DDD 的角度来看,这种建模是否正确,还是会以不同的方式进行?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

诺曦 2024-09-03 18:22:59

创建新项目的过程涉及两次验证(不为空且不重复)。在我上面的设计中,这两个验证分散在两个不同的地方,这使得(恕我直言)更难看到发生了什么。

项目不能也不应该知道应用程序中的所有项目(项目本身不应该知道列表中的所有其他项目),因此 - 它是域服务的责任(而不是应用程序服务。查看 Evans 的书了解一下确切的差异)。

验证有很多种。并且不能'不是通用的验证机制。 DDD 只是说你必须将域验证放在域模型中。

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.

二智少女 2024-09-03 18:22:59

我认为对(1)的部分困惑是您缺少一个层——在您的架构中插入一个服务层,您的问题就会像魔术一样消失。您可以将服务和存储库实现放在服务层中——即,您有一个使用存储库的具体实现的服务。如果需要,其他服务可以自由选择存储库的替代实现。您的应用程序可以自由选择它喜欢的任何服务接口。话虽如此,我不确定在大多数情况下这是否真的重要。在我的几乎所有应用程序中,我都有一个基本上已修复的“域/数据层”。我可能会在其上分层存储库,也可能不分层,具体取决于业务逻辑的复杂程度。与服务相同——如果项目不是很复杂,则可能根本没有必要。如果以后变成这样,我随时可以重构。通常,我会将存储库放在与数据上下文相同的项目中(使用 LINQ),如果有服务,它将位于单独的项目中(因为通常它也会作为 Web 服务公开)。

对于(2),你需要从并发的角度来思考问题。如果可能的话,最好通过数据库约束来检查重复名称。我认为这是执行此逻辑的最简单方法。您当然可以在尝试插入之前检查是否存在重复项,但除非您在事务中执行此操作,否则无法保证另一个进程不会出现并在您的检查和插入之间插入一个重复项。数据库约束解决了这个问题。将检查移至插入逻辑(同一事务)也可以解决问题,但无论如何,我认为您需要准备好将其作为插入失败以及(或代替)验证错误来处理。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文