了解丰富的领域模型和依赖关系

Understanding rich domain models and dependencies

我正在努力了解丰富的域模型以及如何将语义功能构建到域实体中,其中域实体与提供语义行为实现的对象没有紧密耦合

例如,我想在我的域模型中构建一个 User 实体,但我希望它的实现由身份框架驱动

class User
{
    public string Email { get; set; }
    ... All of the other IdentityUser properties...

    public void DisableUser()
    {
        ...behaviour to disable a user, most likely requires UserManager
    }

    public void AddToRole(Role role)
    {
        ... most likely requires RoleManager
    }
}

所以现在我有了一个根据业务规则运行的域模型,并且对持久性和实现一无所知。

但是,当 DisableUser()AddToRole() 没有依赖性并且不以任何方式耦合到 UserManagerRoleManager 时,它们究竟应该如何工作?

我所做的是让我的每个富域模型实体接收对中央域对象的引用作为构造函数参数,并将其存储为 readonly 成员。

这很容易,因为域充当其实体的工厂,所以只要它 new 是其中一个实体,它就会将 this 作为第一个构造函数参数传递。 (实体应该具有程序集内部构造函数,以便它们不能被域本身以外的任何人实例化。)

如果你真的深入研究 ORM 框架的文档,你通常会发现它们往往允许你为你的实体提供一个工厂,所以你可以做那样的事情。

因此,由于每个实体都有对域的引用,它可以从中获取完成其工作所需的任何内容。 (大概,您的域对象将包含对 UserManagerRoleManager 的引用,不是吗?)这实际上是从依赖注入中退一步:您注入域对象及其依赖项,但是您让域的每个实体都从域对象中获取其依赖项。

下面是 java 中的示例:

package ...
import ...

public final class StarWarsDomain extends Domain
{
    private static final Schema SCHEMA = ...

    public StarWarsDomain( LogicDomain logicDomain, S2Domain delegeeDomain )
    {
        super( logicDomain, SCHEMA, delegeeDomain ); //these get stored in final members of 'Domain'
    }

    public UnmodifiableEnumerable<Film> getAllFilms()
    {
        return getAllEntitys( Film.COLONNADE ); //method of 'Domain'
    }

    public Film newFilm( String name )
    {
        assert !StringHelpers.isNullOrEmptyOrWhitespace( name );
        Film film = addEntity( Film.COLONNADE ); //method of 'Domain'
        film.setName( name );
        return film;
    }
}

精心设计的域模型不应该依赖于任何其他架构层或服务。就此而言,域模型对象应该是 (在我的例子中) POCO (普通旧 CLR 对象)。业务逻辑或持久层等服务和层应依赖于这些对象及其 return 个实例。

构建尊重低耦合、高内聚和持久性无知的领域模型有几个关键。在一个声明中,秘诀是“编写您希望拥有的代码”。

域模型示例

public class Student
{
    // Collections should be encapsulated!
    private readonly ICollection<Course> courses;

    // Expose constructors that express how students can be created.
    // Notice that this constructor calls the default constructor in order to initialize the courses collection.
    public Student(string firstName, string lastName, int studentNumber) : this()
    {
        FirstName = firstName;
        LastName = lastName;
        StudentNumber = studentNumber;
    }

    // Don't allow this constructor to be called from code.
    // Your persistence layer should however be able to call this via reflection.
    private Student()
    {
        courses = new List<Course>();
    }

    // This will be used as a primary key. 
    // We should therefore not have the ability to change this value. 
    // Leave that responsibility to the persistence layer.
    public int Id { get; private set; }

    // It's likely that students names or numbers won't change, 
    // so set these values in the constructor, and let the persistence 
    // layer populate these fields from the database.
    public string FirstName { get; private set; }
    public string LastName {get; private set; }
    public int StudentNumber { get; private set; }

    // Only expose courses via something that is read-only and can only be iterated over.
    // You don't want someone overwriting your entire collection.
    // You don't want someone clearing, adding or removing things from your collection.
    public IEnumerable<Course> Courses => courses;

    // Define methods that describe semantic behaviour for what a student can do.
    public void Subscribe(Course course)
    {
        if(courses.Contains(course))
        {
            throw new Exception("Student is already subscribed to this course");
        }

        courses.Add(course);
    }

    public void Ubsubscribe(Course course)
    {
        courses.Remove(course);
    }
}

当然,这个领域模型对象是在考虑 Entity Framework 的情况下编写的,但它与通常的 Entity Framework 示例 (相比之下是贫血的领域模型)相去甚远。在以这种方式制作域模型对象时需要考虑一些注意事项,但 Entity Framework 将持久化它们 (有一点 jiggery-pokery),并且你得到一个域模型对象,它为依赖它的层定义了一个干净的语义契约。