DDD-域模型和实体继承

时间:2019-05-03 20:47:21

标签: c# domain-driven-design

我开始研究DDD,在“深入探究”之前,我想了解一些概念。有人可以帮助澄清一些概念吗?

假设我们有一个用于身份验证的聚合,我们将其称为 AuthenticationAggregate ,在该聚合中,我有一个聚合根,例如 Authentication.cs 。在这个根目录下,我想处理身份验证,但是凭据可以有多种形式,例如,电子邮件/密码( AuthenticationCredentials1 )组合,姓氏/别名( 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 个答案:

答案 0 :(得分:1)

让我们用不同的凭据解决这种情况。做到这一点的一种方法是继承。您的代码将必须检查凭据的不同类型,并在必要时强制转换为具体类型。如果要在投射之前进行一些检查,还可以添加凭据类型代码。这是一个例子。

public enum CredentialsType { type1, type2 }


public interface IAuthenticationCredentials {
    CredentialsType Type { get; }
}

现在关于持久性。这取决于您使用的数据库。如果是RDBMS(例如MySQL),则可以使用single table inheritanceconcrete table inheritanceclass table inheritance

Here's an article on inheritance mapping

如果它像MongoDB一样面向文档,则可以将它们存储为具有不同属性和type属性的文档,以便在进行查询或将它们映射到类时可以区分它们。或者您可以使用其他集合。

ORM确实提供了实现继承映射的不同方法。您将必须根据所使用的具体ORM搜索特定的解决方案。例如,搜索如何在Dapper中进行单表继承。

编辑:强制类型转换:

我是丰富的域模型的支持者,我确实认为对象应该具有行为,而不仅仅是像数据一样对待。最好避免类型转换,因为您可以向类中添加行为并避免这种行为。

也就是说,有时并拒绝 只是数据 。让我们以事件为例。 事件仅是数据而已。其他对象需要获取此数据并进行处理。这是一个示例(为了简化起见,我将避免存储在内存中):

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,但这将导致我们搜索属性而不是类型转换。这样可以避免强制转换,但是仍然使用强类型语言搜索未知对象类型的属性是一个完整的单独讨论。

这里重要的观察是对象的每个 Type (例如 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个?用强类型语言搜索属性并不是一件容易的事。它可能会使您的代码混乱,并且您将失去强类型键入的好处。