0

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?

Doc Brown
  • 19,739
  • 7
  • 52
  • 88

1 Answers1

2

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.

Neil W
  • 7,670
  • 3
  • 28
  • 41
  • I got that an invalid license plate (let's say a plate with a null number) is something different from a spoof license plate, and the constructor should worry about creating a valid plate, not verifying if it is or isn't spoofed. Thanks! Now, I do have a single infrastructure, in the sense that the code that calls the license plate generator API and the code that persists a Car both reside in the same system. However, both responsabilities are in services that are independent from each other. So I suppose I will need to validate the license plate before persisting it, right? – Douglas Monteiro Jan 11 '23 at 20:36
  • Your application layer (command handlers, domain event handlers) will be responsible for the orchestration and choreography of your solution. They will decide which services (external APIs, internal services, etc.) should be used. E.G. The CommandHandler will call the external service for LicensePlate creation and will also instantiate the Car entity and add it to the Car repository. As the CommandHandler should trust the services that it uses then there should be no need for additional validation of license plate. – Neil W Jan 11 '23 at 20:45
  • There is no valid application architecture where just "trust the services that is uses" is a reasonable blanket statement. If the app's risk assessment says it's OK to "just trust the services", that's one thing. But, as a blanket statement, that's absurd. If you need to ensure X comes from Y because Y is the authority, it's 100% fair game and expected that you trace the dots from Y to and ask the question at every layer -- all the way up to and through the domain model to the UI/CLI/DataBase. – svidgen Jan 26 '23 at 19:36
  • Oops. I forgot to include "the question(s)" you should ask *at every layer*: "How can I validate the provenance of this object? How much validation is necessary to meet the business requirements?" – svidgen Jan 26 '23 at 19:47