0

I have read this https://stackoverflow.com/questions/5818898/where-to-put-global-rules-validation-in-ddd and I have create my validator.

But now I need to create a validator that must check the order entity.

This is the problem: When I create or edit an entity "Car", I need to check 2 conditions:

I can't have the property Color with value "Red" if there are 10 or more entity Car with the Color = "Red". I can't have the property Color with value "Red" if the entity "Warehouse" haven't the entry with item code "FF0000" For this check i need to use my ICarRepository for count the item with property Red, and the IWarehouseRepostiory for check if there are an item with code "FF0000".

public class CarValidator : IValidator<Car>
{
    public CarValidator(ICarRepository carRepository, IWahrehouseRepository wahrehouseRepository)
{
.....
}

    private readonly IList<ISpecification<Car>> Rules =
        new List<ISpecification<Car>>
            {
                new AvaiableRedColorSpecification(carRepository, wahrehouseRepository),
            };

    public bool IsValid(Car entity)
    {
        return BrokenRules(entity).Count() > 0;
    }

    public IEnumerable<string> BrokenRules(Car entity)
    {
        return Rules.Where(rule => !rule.IsSatisfiedBy(entity))
                    .Select(rule => GetMessageForBrokenRule(rule));
    }
}

public class AvaiableRedColorSpecification : ISpecification<Car>
{
    public bool IsSatisfiedBy(ICarRepository carRepository, IWahrehouseRepository wahrehouseRepository)
    {
        return carRepository.CountRedColor() < 10 &&
                wahrehouseRepository.HaveItemCode("FF0000");

    }
}

What is the best way to inject the my dependency, because in this way I need to inject the repository inside the Car entity for Add/Edit, I think it's not a good idea inject the repository inside my entity

[EDIT IMPLEMENTATION AFTER READ AL RESPONSE]

I have decide to use Validation Pattern, so I have create a

interface IRuleSpecification<T>
{
    Task<ValidatorResult> IsSatisfiedAsync(T subject);
}
interface IValidator<T, TResult>
{
    Task ExecuteCheckAsync(T entity, TResult validateObject);
    bool IsValid { get; }
    IEnumerable<ValidatorResult> BrokenRules { get; }
    TResult ValidateObject { get; }
}

public static async Task<IValidator<CarDto, CarEntity>> FactoryCreator(CarDto dto, IEnumerable<IRuleSpecification<CarDto>> rules)
{ ... }

In this everytime I try to create entity car, I must pass the IRuleSpecification in the class that have a responsability to call factory (in my case use the CQRS I call CarCreatorCommand).

CarCreatorCommand recive the list of IRuleSpecification by dependy injection.

The implementation class of IRuleSpecification have injected the IRepository for receve the list of color avaiable. In this way the validator have the depence by IReposiory.

3
  • Am I missing something here? Where in your code are you inject the repository inside the Car entity? – Flater Oct 13 '20 at 8:42
  • You probably have a bug in IsValid too, since you return true if there are broken rules. If it breaks the rules, it ain't valid. – Flater Oct 13 '20 at 8:42
  • yes sorry, this code is only an example for try to know the correct way for check the validity in my entity – FedeC87p Oct 14 '20 at 1:06
1

When you inject repositories or other dependencies into the domain, the domain becomes less pure. Impure domain models are harder to test and reason about. Therefore pure domain models are preferred.

So, how would it be possible to satisfy the two conditions below, without injecting a repository?

I can't have the property Color with value "Red" if there are 10 or more entity Car with the Color = "Red".

I can't have the property Color with value "Red" if the entity "Warehouse" haven't the entry with item code "FF0000".

The Car entity could have a method or constructor that takes the required information as arguments, instead of fetching it from a repository. For example:

public static Car Create(Model model, Engine engine, Color desiredColor, IReadOnlyList<ColorCount> usedColors, IReadOnlyList<Color> availableColors)
{
   // do validation on usedColors and availableColors
   return new Car(model, engine, desiredColor);
}

Before calling this method from an application service, you fetch the information from the repository. The usedColors collection contains the number of times each color is used and the availableColors collection contains all the colors in the warehouse.

This way, all decisions can be made inside the domain, without any out-of-process dependencies.

23
  • I like this solution, only one question there is the possibility to call directly Car(model, engine, desiredColor) and bypass the check in this way. There are some pattern o best practice for deny the access do create directly Car() and use everytime the services Create ? – FedeC87p Oct 13 '20 at 10:05
  • Sure, make the constructor private and place the Create factory method inside the Car class. From outside, you call Car.Create(...) which will return a new instance of a Car. – Rik D Oct 13 '20 at 10:17
  • It's completely valid to pass domain services as arguments to methods of domain entities, in order for the domain entity to invoke an appropriate method. The domain models shouldn't have a member variable of such type (making the model's construction quite difficult), but passing them in a transitive manner is fine. – Andy Oct 13 '20 at 10:40
  • @Andy Passing domain services to an entity is fine. It's no neccesary in this case, but there are certainly situations where there's no logical entity to encapsulate business logic, so a domain service can be used. I'm not sure why that's relevant to this answer tbh. – Rik D Oct 13 '20 at 11:33
  • 1
    @Andy Good point. In a real world scenario I wouldn't put primitive types on the Create method, but Value Objects or perhaps an enum, so it wouldn't be so easy to mistakenly pass an incorrect argument by mistake. – Rik D Oct 13 '20 at 14:00
1

There is something missing here. What are the actual rules?

The obvious answer is, of course, that you can create a Car of any Color. You just can't do anything with it! What are you trying to do with your Car? That is, what is the behavior of this system?

Are we trying to Assemble them? Then what? Store them?

class CarFactory 
{ 
    public CarFactory(List<Model> models, List<Engine> engines, List<Color> colors)
     
    public Car Assemble(CarSpec spec)
    {
        //validate specification against available components
    }
}

// VO
class CarSpec
{
    public readonly Model model

    public readonly Engine engine

    public readonly Color color
}

class CarWarehouse
{
    public CarWarehouse(List<Car> cars)

    // alternatively
    public CarWarehouse(Dictionary<Color, int> numberOfEachColor)

    public InventoryTicket Store(Car car)
    {
        //validate the Car's current color against inventory
    }
}

Now we can surmise a use-case like:


// Manufacture Car Handler

var spec = new CarSpec(cmd.Model, cmd.Engine, cmd.Color)

var car = factory.Assemble(spec) // may throw

var ticket = warehouse.Store(car) // may throw

repo.Save(car, ticket)

Importantly, the above isn't focused on the data, but rather, the behavior of our system. We are not worrying about generic "specifications" or "rules" or checking if anything "is valid". We are just embedding our domain logic.. well... directly into the domain! It's simpler this way. Each invariant is being enforced in precisely the same context in which it applies. That is, within the unit of behavior in which it is relevant.

We don't care about the components of a Car until we want to do something with that Car. That's how business processes work: "We can't manufacture (assemble and store) a Car if it doesn't meet some criteria." The fundamental perspective that DDD seeks to provide (really OOP) is that behavior needs to be placed with data, not around data.

Don't validate the data, validate the process.

In this way Validator<Car> really represents the antithesis of DDD. We are choosing to focus on the data (Car) instead of the behavior. We aren't just validating a Car, we are validating a Car within some context. What is that context? I don't see those words in your domain. Something is missing...

0

Apparently, the domain has the concept of a "Rule". These rules transcend the Car entity, but define what a valid warehouse is.

Imagine the requirements would scale up, and a the solution should support several warehouses. In that case each warehouse should support a different set of rules (depending on warehouse size, cultural issues, ...).

So, validation of these Rules is tied to the warehouse's addCar function. Cars should not bother about other cars. Entities should not bother about the aggregates they're in.

The problem you're facing is a direct consequence of this design flaw.

I can imagine the use case should limit the number of available colors for a car in a warehouse; if that's the case, the Rules should probably be an accessible property of the warehouse: wareHouse.availableCarColors.

3
  • But in case of the rule is the name of car is unique (without using the unique key on database) the solution is the same? because this rule is apply with only the Car Domain or in this case we can adopter another solution? – FedeC87p Oct 13 '20 at 12:33
  • 2
    What xtofl is basically saying is that Car shouldn’t be the aggregate root, but Warehouse. Instead of Car.Create something like Warehouse.CreateCar(...). Because Warehouse knows which cars are inside, you can validate the new cars color against the colors of the other cars in the warehouse. This approach can definitely work, but in general we should favor small aggregates. This could potentially load a tremendous amount of data. – Rik D Oct 13 '20 at 15:54
  • @RikD exactly. If the warehouse has a rule about 'used colors', it should probably store (and maintain) the used colors explicitly (or have a DB stored procedure for it, or ...). Thanks for pointing me to the right wording. – xtofl Oct 14 '20 at 7:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.