Понимание богатых доменных моделей и зависимостей

Я пытаюсь разобраться в моделях с расширенными областями и о том, как встроить семантическую функциональность в сущности домена, где сущности домена не тесно связаны с объектами, которые обеспечивают реализации для семантического поведения.

Например, я хочу построить 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() должен работать, когда они не имеют никаких зависимостей и никак не связаны с UserManager а также RoleManager?

  • Вообще чего мне не хватает?
  • Должны ли доменные объекты зависеть от объектов, обеспечивающих поведение?
  • Как мне отделить модель моего домена от поставщиков реализации?

2 ответа

Решение

Хорошо продуманная модель домена не должна зависеть от каких-либо других архитектурных уровней или сервисов. С уважением, объекты модели предметной области должны быть (в моем случае) POCO (Plain Old CLR Objects). Сервисы и уровни, такие как бизнес-логика или уровни персистентности, должны затем зависеть от этих объектов и возвращать их экземпляры.

Есть несколько ключей к построению модели предметной области, которая учитывает низкую связь, высокую когезию и постоянное невежество. В одном утверждении секрет этого заключается в том, чтобы "написать код, который вы хотели бы иметь".

Пример доменной модели

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), и вы получите объект модели домена, который определяет чистый, семантический контракт для слоев это зависит от этого.

Я делаю так, чтобы каждый из моих объектов модели расширенного домена получал ссылку на объект центрального домена в качестве параметра конструктора и сохранял его как readonly член.

Это легко, потому что домен действует как фабрика своих сущностей, поэтому всякий раз, когда он news один из них, это проходит this в качестве первого параметра конструктора. (Предполагается, что сущности имеют внутренние конструкторы сборки, чтобы их никто не мог создать, кроме самого домена.)

И если вы действительно углубитесь в документацию ORM-фреймворков, вы, как правило, обнаружите, что они, как правило, позволяют вам предоставлять фабрику для ваших сущностей, поэтому вы можете делать такие вещи.

Таким образом, поскольку каждая сущность имеет ссылку на домен, она может получить от нее все, что ей нужно для выполнения своей работы. (Предположительно, ваш доменный объект будет содержать ссылку на UserManager и к RoleManager, нет?) По сути, это прагматичный шаг назад от внедрения зависимостей: вы внедряете объект домена с его зависимостями, но каждая сущность домена получает свои зависимости от объекта домена.

Вот пример в 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;
    }
}
Другие вопросы по тегам