It's difficult to talk in abstract terms, so here's an example that might help. Imagine we have a service that lets Car
s take trips. During this trip, Car
s can run out of gas and need to change oil.
class Car {
private GasTank gasTank;
private OilPan oilPan;
double MPG = 25.0;
void drive( int distance ) {
gasTank.consume( distance / MPG );
oilPan.dirty( distance / OilDirtyRate ); // Making up a term here...but Oil gets dirty
}
void fillUp() {
gasTank.fillUp();
}
void changeOil() {
oilPan.empty();
oilPan.add( new Oil() );
}
}
// These are value objects, there's no identity here
class GasTank { } // Imagine above methods defined
class OilPan { } // more methods
class CarRepository {
Car findByVIN( String vin ) {
// Search Car collection or database
return car;
}
boolean register( Car car ) {
carDao.insert( car.toDTO() );
}
boolean update( Car car ) {
// write to persistence, etc.
}
}
class TripService {
private CarRepository carRepo;
void takeTrip( String carVin, int milesToGo ) {
Car car = carRepo.findByVIN( carVin );
// calculate distance
while ( milesToGo > 0 ) {
car.drive( 200 );
milesToGo -= 200;
if ( car.isOutOfGas() ) {
car.fillUp();
}
if ( car.needsOilChange() ) {
car.changeOil();
}
}
carRepo.update( car );
}
}
I apologize for using yet another car example (and Java, since you're speaking C#), but you can see that Car still has logic, but the TripService can manipulate the Car as a whole. The Car is responsible for dealing with it's internal logic, such as consuming gas and dirtying the oil. Maybe drive
returns the distance actually covered, if there isn't enough gas in the tank. Maybe it throws an Exception, if it isn't a normal occurrence. The drive
method could also check if the car is started, has oil, has gas, and seat beats are buckled. Starting the car could consume gas at a different rate. You can see that the TripService (the 'application') talks to CarRepository
and manipulates Car
, but Car
knows nothing about CarRepository
, yet still has logic.
Handling out of gas and oil changes may not be the resposibility of a TripService
in real life, but in an application that simulated car wear-and-tear, it might assume those responsibilities.
To step back to the abstract terms, your services interact with repositories and factories to obtain domain objects. Then they orchestrate the interactions between domain objects where needed. After all, something has to tell your domain objects what to do. That's where services come into play. The domain objects know nothing about repositories, since they're persistence ignorant. Knowing how itself is persisted doesn't affect the business logic, so you don't handle that within the domain objects. They just handle business logic and maintaining invariants. Repositories have to know about domain objects, since they're persisting and retrieving them.