Reputation: 505
I'm starting to look at DDD and I would like to understand some concepts before I "deep dive" into it. Can someone help clarify some concepts?
Let's say we have an aggregate for authentication let's call it AuthenticationAggregate, in that aggregate I have an aggregate root let's say Authentication.cs. In this root I want to handle the authentication but credentials can come in many forms, for instance, email/password (AuthenticationCredentials1) combo, last name/dob (AuthenticationCredentials2), etc.
How would an implementation of say IAuthenticationRepository work? Should I create a property of IAuthenticationCredentials entity on the aggregate root? If so, how can I handle the different properties of the different authentication credentials?
Thank you for your help in advance.
--------------------- edited for clarity ---------------------
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....
}
}
Upvotes: 2
Views: 2371
Reputation: 2290
Let's address the situation with different credentials. One way you can do this is with inheritance. Your code will have to check for the different types of credentials and do casting to the concrete type if necessary. You can also add credentials type code if you want to do some checks before casting. Here's an example.
public enum CredentialsType { type1, type2 }
public interface IAuthenticationCredentials {
CredentialsType Type { get; }
}
Now about persistence. It depends on the database you're using. If it's a RDBMS (MySQL for example) you can use single table inheritance, concrete table inheritance, class table inheritance.
Here's an article on inheritance mapping
If it's document oriented like MongoDB, you can store them as documents with different properties and type property so you can differentiate between them when you make queries or when you map them to classes. Or you can use different collection.
ORMs do provide different ways you can achieve inheritance mapping. You will have to search for a specific solution based on the concrete ORM you use. For example search for how to do single table inheritance in Dapper.
Edit: On type casting:
I'm a proponent of rich Domain Models and I do think that object should have behavior, not just be treated like data. Type casting is best avoided as you can add behavior to classes and avoid it.
That said, sometimes and oject is just data. Let's take for example Events. An Event is just data and that's it. Other object need to get this data and process it. Here's an example (I'll avoid storage, just in memory to simplify things):
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
}
}
In the above example we can notice couple of things.
Because we are using strong typed language C# in this case, when we do the EventHistory
we need to define our Queue
with IEvent
type so we can store multiple different types of objects indside. Our Dequeue
method will need to return IEvent.
Our EventProcessor
will use a map from event type to eventHandler. This map need to be declared as Dictionary<Type, Action<IEvent>>
. In order for us to store a delegate to a method, the method need to be defined void handler(IEvent)
. Because we need to process the concrete event, we need to cast. An alternative is to use dynamic
, but this will cause us to search for properties instead of type casting. This avoids the casting but still, searching for a property in an unknown object type in strong typed language is a whole separate discussion.
The important observation here is that each Type of an object (for example AccountAddedEvent) represents a thing that contains specific properties that another object (the EventProcessor) will need. The processor is not just interested in the properties, but it also need to know the type of the event in order to process it.
In this case type casting is OK. If we were using a loose typed language like JavaScript, we would just add a property eventType to an object and switch on it. Then we can get each property without type casting.
In your example there isn't much information on the specific scenarios how these classes will be used, so I assumed another object in your code will need the data and type of the login so it can interpret them. The credentials themselves won't have much behavior. If I'm mistaken, you need to give a detailed example and scenarios that will tell us more about how they will be used.
An alternative is to use an object that respresents just a bag of data. Like objects in JavaScript that you can just stick a property to it and it's either there or not.
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) {}
}
If you don't need types like (UserNameAndPassword, EmailAndCode, PhoneAndCode), your processing code will just search for properties. If you need a type you can always add it as a property and check it.
Personally If you need to know specific types of credentials I would choose type casting. I don't think that you will have that many types of logins. Maybe 3 or 5 max? Searching for properties in strong typed languages isn't pretty. It can muddle your code and you will loose the benefits of strong typing.
Upvotes: 2