Search code examples
design-patternsdomain-driven-design

Creating a domain object from an Infrastructure service and apply business logic


I'm tasked with developing an application which is going to track what an end user does in a session.

My application doesn't handle user registration or anything related to user. The user management is a different bounded context(a separate micro service in my case.) But my application would need to identify an user from a user card(my application would receive the card information.). I would need to call the User micro service with the card information to get user details back. And with the details received, I would need to apply business rules(these rules are part of my domain) to verify if this user can create a session or not.

This is my handler in the Application layer,

public async Task<int> Handle(CreateSessionCommand request, CancellationToken cancellationToken)
    {
        //Get Player details from third party service from the Infrastructure layer
        var playerSearch = await _playerService.GetPlayerByCardAsync(request.CardTrack);

        //Create a domain object from the search details.
        Player player = new Player(playerSearch.Id, playerSearch.Name, playerSearch.Age);

        //Apply domain rules 
        if (player.CanCreatePlayerSession())
        {
            //
        }
        return 0;
    }

This is my Player domain entity,

public class Player
{
    public Player(int id, string name, int age)
    {
        Id = id;
        Name = name;
        Age = age;
    }

    public int Id { get; private set; }
    public string Name { get; private set; } = string.Empty;
    public int Age { get; private set; }

    public bool CanCreatePlayerSession()
    {
        return this.Age > 18;
    }
}

Is creating a domain object from a Infrastructure service a good DDD approach? Are there any better ways of doing something similar?


Solution

  • Consider the following: is Player owned by your domain or by some external system's domain?

    If your system owns Player, the latter should basically reflect GetPlayerByCardAsync's response (PlayerService constructs full player object for you, probably by calling another api or directing querying a storage).

    But also if Player is owned by an external system, or by a legacy system within your domain, you should count on that system to construct this object.

    So we can see that in all above cases a somewhat external component, encapsulated inside an infrastructure service, is responsible for the construction of Player. And deserialization is performed directly inside that infrastructure service.

    But when dealing with external/legacy systems, it is sometimes necessary to perform adaptations (the anti-corruption layer pattern). From your code sample I can assume that you have that need, since you present the method CanCreatePlayerSession. That actually means that you have two separate domain objects for a player: the one that returns from PlayerService, and the one constructed by you. I would recommend to use more accurate semantics, e.g. CardPlayer and SessionPlayer.

    The instruction to construct SessionPlayer object, however, should, in line with your code sample, come from the application layer, as it is part of application flow.