DDD - Доменная модель и наследование сущностей - PullRequest
0 голосов
/ 03 мая 2019

Я начинаю смотреть на DDD и хотел бы понять некоторые концепции, прежде чем углубиться в него.Может кто-нибудь помочь прояснить некоторые понятия?

Допустим, у нас есть агрегат для аутентификации, назовем его AuthenticationAggregate , в этом агрегате у меня есть корень агрегата, скажем Authentication.cs .В этом корне я хочу обработать аутентификацию, но учетные данные могут иметь различные формы, например, адрес электронной почты / пароль ( AuthenticationCredentials1 ), фамилия / dob ( AuthenticationCredentials2 ) и т. Д.

Как будет работать реализация, скажем, IAuthenticationRepository ?Должен ли я создать свойство объекта IAuthenticationCredentials в совокупном корне?Если да, как я могу обрабатывать различные свойства различных учетных данных для аутентификации?

Заранее благодарю за помощь.

--------------------- отредактировано для ясности ---------------------

Authentication.cs

public class Authentication : IAggregateRoot
{
    //uncertain of this
    private IAuthenticationCredentials _authenticationCredentials;
    public IAuthenticationCredentials AuthenticationCredentials => _authenticationCredentials;

    protected Authentication()
    {
    }

    public void Authenticate(string email, string password)
    {
        _authenticationCredentials = new AuthenticationCredentials1(email, password);
    }

    public void Authenticate(string name, DateTime dob)
    {
        _authenticationCredentials = new AuthenticationCredentials2(name, dob);
    }
}

AuthenticationRepository.cs

public class AuthenticationRepository : IRepository<Authentication>
{
    private readonly IDatabase _db;

    public AuthenticationRepository(IDatabase db)
    {
        _db = db ?? throw new ArgumentNullException("db");    
    }

    public async Task<Authentication> Authenticate(Authentication authenticationAggregateRoot)
    {
        //persistence logic here
        //say if I use a micro-orm like dapper, how do I populate the where clause based on
        authenticationAggregateRoot.AuthenticationCredentials....
    }
}

1 Ответ

1 голос
/ 04 мая 2019

Давайте рассмотрим ситуацию с разными учетными данными.Один из способов сделать это - наследование.Ваш код должен будет проверить различные типы учетных данных и при необходимости выполнить приведение к конкретному типу.Вы также можете добавить код типа учетных данных, если вы хотите сделать некоторые проверки перед приведением.Вот пример.

public enum CredentialsType { type1, type2 }


public interface IAuthenticationCredentials {
    CredentialsType Type { get; }
}

Теперь о постоянстве.Это зависит от базы данных, которую вы используете.Если это СУБД (например, MySQL), вы можете использовать наследование одной таблицы , наследование конкретной таблицы , наследование таблицы класса .

Вот статья о отображении наследования

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

ORM предоставляют разные способы достижения наследования.Вам придется искать конкретное решение на основе конкретной ORM, которую вы используете.Например, поиск как сделать наследование одной таблицы в Dapper.

Редактировать: При приведении типа:

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

При этом иногда и oject - это просто данные .Давайте возьмем для примера События . Событие - это просто данные и все.Другой объект должен получить эти данные и обработать их.Вот пример (я буду избегать хранения, просто в памяти, чтобы упростить вещи):

public interface IEvent { // empty, just a marker interface }

public class AccountRegisteredEvent : IEvent {
    public Guid AccountGuid { get; private set; }
    public Email AccountEmail { get; private set; }
    public string UserName { get; private set; } 
}

public class CommentAdded : IEvent {
    public GuidAccountGuid { get; private set; }
    public string Comment { get; private set; }
}

public class EventHistory {

    private readonly Queue<IEvent> mEvents;

    public void Enqueue(IEvent event) { ..... }

    public IEvent Dequeue() {
        return mEvents.Dequeue();
    }
}

public class EventProcessor {
    private Dictionary<Type, Action<IEvent> mEventTypeHanlerMap;

    public EventProcessor() {
        mEventTypeHandlerMap = new Dictionary<Type, Action<IEvent>();

        meventTypeHandlerMap.Add(
            typeof(AccountAddedEvent),
            ProcessAccountAdded);
        // add other event handlers
    }

    public void Process(IEvent event) {
        var handler = mEventTypeHanlerMap[event.GetType()];
        handler(event);
    }

    private void ProcessAccountAdded(IEvent event) {
        var accountAddedEvent = (AccountAddedEvent)event;
        // process
    }

    private void ProcessCommentAdded(IEvent event) {
        var commentAdded = (CommentAddedEvent)event;
        // process
    }
}

В приведенном выше примере мы можем заметить пару вещей.

  • Поскольку в этом случае мы используем язык C # со строгой типизацией, когда мы делаем EventHistory, нам нужно определить наш Queue с типом IEvent, чтобы мы могли хранить несколько различных типов объектов внутри.Наш метод Dequeue должен вернуть IEvent.

  • Наш EventProcessor будет использовать карту от типа события до eventHandler.Эта карта должна быть объявлена ​​как Dictionary<Type, Action<IEvent>>.Чтобы мы могли сохранить делегат в методе, метод должен быть определен void handler(IEvent).Поскольку нам нужно обработать конкретное событие, нам нужно выполнить приведение.Альтернативой является использование dynamic, но это заставит нас искать свойства вместо приведения типов.Это позволяет избежать приведения, но, тем не менее, поиск свойства в неизвестном типе объекта на языке строго типизированного текста - это отдельное обсуждение.

Важное наблюдение здесь заключается в том, что каждый Тип объекта (например, AccountAddedEvent ) представляет вещь , которая содержит определенных свойств , которые понадобятся другому объекту ( EventProcessor ),Процессор не просто интересуется свойствами, но также должен знать тип события для его обработки.

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

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

Альтернативой является использование объекта, который представляет только пакет данных.Как и объекты в JavaScript, вы можете просто прикрепить к нему свойство, и оно либо есть, либо нет.

public class AuthenticationCredentials {

    private Dictionary<string, string> mData;

    public bool HasProperty(string propertyName) {}
    public void SetValue(string propertyName, string value) {}
    public string GetValue(string propertyName) {}
}

Если вам не нужны такие типы, как (UserNameAndPassword, EmailAndCode, PhoneAndCode), ваш код обработки простопоиск недвижимости.Если вам нужен тип, вы всегда можете добавить его в качестве свойства и проверить его.

Лично Если вам нужно знать конкретные типы учетных данных, я бы выбрал приведение типа. Я не думаю, что у вас будет так много типов логинов. Может быть 3 или 5 макс? Поиск свойств в строго типизированных языках не очень хорош. Это может запутать ваш код, и вы потеряете преимущества строгой типизации.

...