3

Imagine you have a Vehicle entity in your domain model. Vehicle entity has Reserve method that put vehicle in "reserved" state and do another stuff. But Reserve method have to do some checking first to ensure that reservation could be done. This checking is done by legacy stored procedure that have to be called as a part of reservation process. Stored procedure call is encapsulated in repository method.

The question: Should I pass repository as a parameter of domain entity method if method interacts with data storage? Are there any drawbacks of such a solution? Are there alternatives?

The sample:

class VehicleRepository: IVehicleRepository {
    public bool IsReserveAvailable() {
        // call stored proc here
    }
}

class Vehicle {
    public void Reserve (IVehicleRepository vehicleRepository) {
        bool isReserveAvailable = vehicleRepository.IsReserveAvailable();
        if (isReserveAvailable) {
            // do stuff ...
        }
    }
}

2 Answers2

5

The advantage of having the Method on the vehicle class is that it matches your business language. "I want to reserve the vehicle please!"

The disadvantage is that whenever you have a vehicle, you have to also have the dependent service around in case you want to reserve it.

If you can refactor it out of the sproc so that it is only a logic operation on the members of Vehicle great.

But if you can't, because the operation relies on information outside of the Vehicle, such as knowledge of all other vehicles, then you might want to consider a VehicleReservationService class which you pass the Vehicle object to.

After all, you are really only hiding the existence of this service with Vehicle.Reserve(IDependency repo). Sometimes the business language is wrong and needs to change,

Clarification:

No. You should not pass the repository. Either move code out of the db if it can be made a pure function of Vehicle. Or create a VehicleResevationService to deal wth reservations.

Sample:

public class Vehicle
{
    public bool Reserve()
    {
        if(this.x && this.y)
        {
            this.Status = "reserved";
            return true;
        }
        return false;
    }
}

public class VehicleReservationService
{
    public VehicleReservationService(IRepository repo)
    {
        this.repo = repo;
    }

    public bool Reserve(Vehicle v)
    {
        return repo.Reserve(v.Id, v.OtherParametersOfSproc);
    }
}
Ewan
  • 83,178
3

I'm personally not in a group of people who think that Entities should not access Repositories. So to me, the answer to your question is "Yes, sure, go ahead."

But in case you don't want to do that, I would like to change Ewan's solution slightly.

// notice this is not general vehicle repository
public interface IVehicleReservationRepository
{
    bool IsReserveAvailable();
}

public class Vehicle
{
    // notice Reserve being private
    // should only be called when reservation can happen
    private void Reserve()
    {
        // do stuff ...
    }

    public class ReservationService
    {
        private IVehicleReservationRepository _vehicleReservationRepo;

        public ReservationService(IVehicleReservationRepository vehicleReservationRepo)
        {
            _vehicleReservationRepo = vehicleReservationRepo;
        }

        public void Reserve(Vehicle v)
        {
            bool isReserveAvailable = _vehicleReservationRepo.IsReserveAvailable();
            if (isReserveAvailable)
            {
                v.Reserve();
            }
        }

        public void ReserveAll(IEnumerable<Vehicle> vehicles)
        {
            // efficient reservation for multiple vehicles
            // doesn't need to call repository for every vehicle
        }
    }
}

By making the RegistrationService nested of Vehicle, it allows it to access it's private members. This is because making the service nested to the entity couples them together. So the service is integral part of the entity. It is still possible to use DI to create the ReservationService and having it separate like this makes it clear what operations make use of the repository service.

Another thing to point out is that instead of using generic IVehicleRepository, I created special IVehicleReservationRepository, that is only used in this use case. In the end, it might be implemented on VehicleRepository, but that is unrelated to this problem. Making interface specific for one use case is good usage of interface segregation principle and would make testing easier, as you don't need to worry about other methods present on full repository.

Last thing that came to my mind is scenario, where you might want to reserve multiple vehicles. If you had Reserve method on a Entity, you would have to call it on each vehicle, calling repository, and thus database, for every entity. But having separate service allows you to optimize this by calling repository once for multiple vehicles. As seen in ReserveAll method on the ReservationService.

Euphoric
  • 38,149