DDD中的封装

问题描述 投票:0回答:1

在设计聚合和实体时,最好只向消费者提供聚合的公共属性和方法。但碰巧为了改变某些属性,需要执行复杂的业务逻辑案例。

例如,我们有一个用户聚合,它有一个电子邮件属性。要更改电子邮件,您需要确保没有其他注册用户使用同一电子邮件。这意味着您需要运行带有数据库搜索的复杂脚本。将存储库(或存储库接口)嵌入到聚合本身的方法中是一种不好的做法,我们会得到进程外依赖项。因此,您需要将此代码放在 DomainService 或 AppService 中(取决于方法)。这意味着该属性将是内部或公共的,并且可以在程序集内部或外部进行更改。这意味着任何人都可以错误地更改它,绕过服务中描述的业务规则。

public class User : IAggregateRoot
{
    public int Id { get; }
    public string Email { get; set; }

    private User(int id, string email)
    {
        Id = id;
        Email = email;
    }
}

public class UserService
{
    private IUserRepository repository;

    public UserService(IUserRepository repository)
    {
        this.repository = repository;
    }

    public async Task ChangeEmail(int userId, string email)
    {
        var exist = await repository.GetByEmailAsync(email);
        if (exist != null)
            throw new DomainException("Email is already in use");

        var user = await repository.GetByIdAsync(userId);
        user.Email = email;
        await repository.SaveAsync(user);
    }
}

如果该财产是私人的,可以进行修改,那就太好了。从其他类的访问只能通过 ChangeEmail 方法。

public string Email { get; private set; }

如果 C# 中存在友好类,则可以解决此问题,然后可以向服务授予对聚合或实体的私有属性和方法的访问权限。

如果程序集可以相互引用(如图所示),这个问题也可以得到解决,但 C# 中禁止循环依赖。 (图中的每个方块都是一个单独的项目)

enter image description here

您可以尝试使用可以访问私有属性和方法的嵌套类,但是对于聚合和与聚合关联的实体来说,嵌套类不能相同。例如,Order 是聚合,OrderItem 是实体。它不能同时嵌套在两个类中。

c# domain-driven-design encapsulation rich-domain-model
1个回答
0
投票

你可以这样做:

public class User : IAggregateRoot
{
    public int Id { get; }
    public string Email { get; private set; }

    private User(int id, string email)
    {
        Id = id;
        Email = email;
    }

    public void ChangeEmail(string email)
    {
        Email = email;
        //... maybe trigger a domain event here
    }
}

public class UserService
{
    private IUserRepository repository;

    public UserService(IUserRepository repository)
    {
        this.repository = repository;
    }

    public async Task ChangeEmail(int userId, string email)
    {
        var exist = repository.GetByEmailAsync(email);
        if (exist != null)
            throw new DomainException("Email is already in use");

        var user = await repository.GetByIdAsync(userId);
        user.ChangeEmail(email);
        await repository.SaveAsync(user);
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.