A service encapsulates a business action whose rules span one or more entities. In a backend, controllers translate requests into domain commands; entities model business concepts and protect their own state, while services handle the more complex rules that do not fit cleanly inside one entity. section 1.4
Use a service when an operation needs information outside an entity, especially database-backed checks. Keep every consistency check an entity can enforce locally in that entity; move the entire action and its extra checks to the service when external knowledge or complexity would otherwise overload the entity. Clients should call the service rather than attempting checks themselves. section 3.1.2
A service commonly coordinates an action in this order:
- Retrieve the required entities or accept them from the caller, and reject invalid or missing inputs. section 3.2.4
- Run business validation and consistency checks before changing state. section 3.2.2
- Perform the entity operation only after validation succeeds. section 3.2.4
- Delegate reusable or complicated rules to validation components, but do not create them prematurely for feature-specific checks. section 3.2.2
This separation controls entity complexity and avoids coupling entities directly to databases or other external dependencies. The tradeoff is discoverability: behavior has moved away from the data it affects, so callers must know to use the service and nearby documentation should point them there. It is a practical choice for complex actions, not a reason to extract every small rule from an entity. section 3.1.2 section 3.2.2
Registering an employee for an offering
An AddEmployeeToOfferingService loads an offering and employee, verifies that both exist, invokes a validator, and adds the employee only when validation succeeds. The validator checks available places, prior participation in the training, and duplicate registration. The offering still owns checks it can make from its own state; the service coordinates rules requiring training-history data. section 3.2.4 section 3.2.2
Listing 3.5 AddEmployeeToOfferingService class
class AddEmployeeToOfferingService {
private OfferingRepository offerings;
private EmployeeRepository employees;
private AddEmployeeToOfferingValidator validator;
public void addEmployee(int offeringId, int employeeId) {
var offering = offerings.findById(offeringId);
var employee = employees.findById(employeeId);
if(offering == null || employe == null) #1
throw new InvalidRequestException(
"Offering and employee IDs should be valid");
var validation = validator.validate(offering, employee); #2
if(validation.hasErrors()) { #3
throw new ValidationException(validation);
}
offering.addEmployee(employee); #4
}
}
The listing makes the coordination boundary concrete: lookup, validation, failure handling, then the entity mutation. section 3.2.4






