0

I'm new to DDD and right now I'm in the process of implementing user registration.

Basically the flow is:

  1. Controller receives request
  2. Maps the received data from client to User Domain model.
  3. Maps the User Domain model to an EF-core entity model.
  4. Adds user to database using EF-Core.

I'm having some doubts regarding where I place IdGeneratorService class and BCryptService class. Currently both of them have an Interface in Domain layer: src/Domain/Model/Services. Their implementation is residing inside my Infrastructure layer: src/Infrastructure/Services. They are also both being called inside the User domain model:

public class User
{
    private User(
        long id, 
        string name, 
        string lastName, 
        string contactPhone, 
        string contactEmail, 
        string userName, 
        string password)
    {
        Id = id;
        Name = name;
        LastName = lastName;
        ContactPhone = contactPhone;
        ContactEmail = contactEmail;
        UserName = userName;
        Password = password;
    }

    public IEnumerable<Role> Type { get; set; }

    public static async Task<User> ConstructAsync(
        string name, string lastName, 
        string contactPhone, string contactEmail, 
        string userName, string password, 
        IIdGeneratorService<long> idGeneratorService, IBCryptService bCryptService)
    {
        var id = await idGeneratorService.GenerateAsync();

        password = bCryptService.HashPassword(password);

        return new User(id, name, lastName, contactPhone, contactEmail, userName, password); 
    } 

This is being called by my controller:

[HttpPost("[Action]")]
public async Task<IActionResult> Create([FromBody] UserModel userModel)
{
    var user = 
        await DomainUser.ConstructAsync(
            userModel.Name, userModel.LastName,
            userModel.ContactPhone, userModel.ContactEmail,
            userModel.UserName, userModel.Password,
            _idGeneratorService, _bCryptService);

    await _userRepository.AddAsync(user);

    return sampleResponse;
}

I feel like calling the IdGenerator and BCrypt inside domain model is bad practice, because from what I understand the Domain layer cannot know about the Infrastructure layer, so I'm not really sure how I go about this. Any other help/suggestions regarding other implementations based on what you understood here is greatly appreciated.

jww
  • 97,681
  • 90
  • 411
  • 885

1 Answers1

4

It is a good practice to introduce an intermediate "Application Service" layer between the Controller and the Domain Layer.

The App Service will be responsible for invoking the (injected) infrastructure services, calling the domain layer and also persisting/loading necessary data. The Controller's responsibility is only to gather the request params, ensure authentication (if needed), and then make the call to the Application Service method.

Application Services are the direct clients of the domain model and act as intermediaries to coordinate between the external world and the domain layer. They are responsible for handling infrastructure concerns like ID Generation, Transaction Management, Encryption, etc. Such responsibilities are not concerns of the Controller layer either.

App services also typically do task coordination for APIs (one service method per API). When using an ACID database, the same Services also control transactions, ensuring that model state transitions are atomically persisted.

In your specific example, the App Service would be responsible for invoking the IdGeneratorService and BCryptService, getting the output and sending them as parameters to the domain layer. As far as the Domain layer is concerned, it does need to know how exactly the ID or the password were generated - those are not domain concerns. So it happily stays oblivious to these infrastructure aspects and only burdens itself with the tasks of User Behavior.

Subhash
  • 3,121
  • 1
  • 19
  • 25
  • Thank you for your response, it's very easy to understand. I have a question: You said the Domain layer does not need to know how an Id or hash is generated, but these services are used only for generating data related to Domain models (at least in my example, i will not be using these 2 services for nothing else). With that in mind, would it make sense to put the implementation of these 2 services in the Domain layer? And if not, what would be an example of a service in the Domain layer? – G. Oliveira Aug 15 '19 at 23:22
  • 2
    When you think about whether a concept belongs in the domain layer, a question I have found to be useful to ask what would happen if you swap implementations? In your example, would your domain behavior and functionality "change" if you changed identifiers from integers to GUIDs, or from Bcrypt to SHA256? If the answer is no, it's best to keep them outside the domain layer. I know it's tempting to include them in the domain layer, but over the long run, you will reap benefits from keeping them outside. – Subhash Aug 15 '19 at 23:41
  • 1
    Services in the domain layer are rarely concerned with such implementation related aspects. One good example I can think of is a unique Order ID generation. You may have an Order ID made up of individual parts - a Store Identifier, Date, Running sequence number. Such a generation service should be part of your domain layer because the order ID is a domain concept and is related to other domain elements. – Subhash Aug 15 '19 at 23:45
  • Again, thank you for the responses! Your second comment raised me this question: Isn't the Order Id pretty much the same as the User id in question? As in both are generating unique Id's to a specific domain element. – G. Oliveira Aug 15 '19 at 23:58
  • 1
    Apologies, I might have assumed a bit here. If your UserID is a running sequence number or a GUID, then it is not "related" to the domain. It's an arbitrary, unique identifier generated without any help from the domain itself. But OrderID uses other domain concepts in its generation process, and that's the difference. Was I wrong in my assumption on UserID? – Subhash Aug 16 '19 at 00:01
  • Aaah yes, I understood what you meant. And yes, I am using [RobThree's IdGen](https://github.com/RobThree/IdGen) – G. Oliveira Aug 16 '19 at 00:22
  • 1
    Ok. `IdGen` seems to be deterministic (and not random), but it is still independent of the domain - It doesn't need to refer to other domain concepts. I would suggest you treat this akin to a unique identifier and place it outside the domain layer. If you, one day, want to use a different ID generator, it will be a straightforward and effortless swap. – Subhash Aug 16 '19 at 00:28
  • 1
    Further read on encapsulating identifiers as Value Objects: https://buildplease.com/pages/vo-ids/ – Subhash Aug 16 '19 at 00:35
  • That was a great read, I'll keep that in mind. So I had some time to think and I believe that even the interfaces of the 2 mentioned services should be in Infrastructure layer, because they don't 'model' the behavior of any Domain elements, contrary to Repositories interfaces for example, where they 'model' operations on the Domain elements. But, should the Infrastructure layer determine external service behavior (`IdGen` and `BCrypt` in this case) AND implement them? – G. Oliveira Aug 16 '19 at 08:32
  • 1
    Yes, it is better to have the interfaces outside in the infrastructure layer. Your domain remains clean, that way. Yes, the infrastructure layer is supposed to handle the complexities associated with concrete implementations and keep them from leaking into your domain layer (Encapsulated). – Subhash Aug 16 '19 at 14:33
  • 1
    Of course, it goes on to say that the infrastructure layer also supplies different interfaces for different use cases. For example, you may use `IdGen` for most of your identifiers but may want to use GUID for say, Events. Your Event application service will use the appropriate concrete implementation. But in the domain layer, your Event model will have the same concept as far as the code is concerned - a unique identifier. – Subhash Aug 16 '19 at 14:34