Search code examples
oopdomain-driven-designclean-architecturehexagonal-architecture

Should I restrict the construction of a domain object to an external service?


Let's say I have the value object LicensePlate. It is part of a Car, which is an entity in my domain. However, the logic for building the plate doesn't belong to my domain, I simply obtain that from a domain service RegistrationAgency.obtainPlate(Car car), implemented in the infrastrucure layer as DMV.obtainPlate(Car car), which calls an external API.

Now, I feel like I should restrict the construction of the LicensePlate to the service, so I can be sure that any instance of LicensePlate is valid (i.e was made by a registration agency). Is that a justified concern?

Anyway, the solutions I can think of is making LicensePlate's constructor private and adding to the class a static factory method, let's say LicensePlate.build(car, licenseNumberFactory), with LicenseNumberFactory being the one responsible for calling the external API. How messy is that? What about the DDD? Am I respecting it? Should I just make LicensePlate public instead and avoid all of this?


Solution

  • Should I just make LicensePlate public instead and avoid all of this?

    Yes

    The value object LicensePlate should be able to enforce its own invariants, e.g. cannot be null / must contains numbers and letters / whatever else.

    public class LicensePlate
    {
        public string RegNumber { get; init; }
        public LicensePlate(string regNumber)
        {
            if (string.IsNullOrWhitespace(regNumber))
                throw ArgumentOutOfRangeException(nameof(regNumber));
    
            // perform other invariant checks;
    
            RegNumber = regNumber;
        }
    }
    

    Now you have a license plate that enforces its own invariants (within its sphere of knowledge, at least). Your car entity will look something like:

    public class Car
    {
        public string Model { get; private set; }
        public string Color { get; private set; }
        public LicensePlate LicensePlate { get; private set; }
    
        public Car(string model, string color, LicensePlate licensePlate)
        {
            Model = model;
            Color = color;
            LicensePlate = licensePlate;
        }
    }
    

    so I can be sure that any instance of LicensePlate is valid (i.e was made by a registration agency)

    If registration agency means that the plate must have been created by a trusted service then that is up to the infrastructure to enforce.

    You might think that any caller could have created a license plate to put on your car entity. This is true. But, if that caller does not have access to your infrastructure (database) then creating that entity does not cause any risks as the caller (infrastructure) that may have provided a spoof license plate cannot persist it in your infrastructure (database).

    If the same infrastructure codebase that has access to your database is used to make the call to the license plate generation API, then all is good.

    If a completely different infrastructure wishes to make use of the Car entity but with license plates created by a different service (or a mock service when testing), then that is up to the infrastructure / application layer. Indeed, this is a feature of DDD layering. The Car entity cannot be expected to enforce invariants that are outside of its control (e.g. whether the value object was acquired from a specific external service).

    Anyway, the solutions I can think of is making LicensePlate's constructor private and adding to the class a static factory method, let's say LicensePlate.build(car, licenseNumberFactory)

    You could do that, but you still don't know if the licenseNumberFactory itself is spoofed by the infrastructure layer.

    And you don't want your entity model to have to know about infrastructure implementation.