There are cases when according to our business domain, and the rules governing it, some concepts can not be modeled as part of existing entities; in those cases there's the need to introduce Services.
Although a really overloaded term, a Service, in the context of Domain Driven Design, defines assertions in the ubiquitous language of a specific bounded context.
In Domain Driven Design Ubiquitous Language is defined by Eric Evans as (emphasis mine):
A language structured around the domain model and used by all team members within a bounded context to connect all the activities of the team with the software.
And Bounded Context as:
A description of a boundary (typically a subsystem, or the work of a particular team) within which a particular model is defined and applicable.
Disclaimer: This post includes Amazon affiliate links. If you click on one of them and you make a purchase I'll earn a commission. Please notice your final price is not affected at all by using those links.
How does a service look like in practice?
If we look at our code for our "To Do Microservice", we implemented a service.Task
that looks like:
// Task defines the application service in charge of interacting with Tasks.
type Task struct {
repo TaskRepository
}
// Create stores a new record.
func (t *Task) Create(ctx context.Context, description string, priority internal.Priority, dates internal.Dates) (internal.Task, error) { /* ... */ }
// Task gets an existing Task from the datastore.
func (t *Task) Task(ctx context.Context, id string) (internal.Task, error) { /* ... */ }
// Update updates an existing Task in the datastore.
func (t *Task) Update(ctx context.Context, id string, description string, priority internal.Priority, dates internal.Dates, isDone bool) error { /* ... */ }
This service references a Repository called TaskRepository
which eventually gets assigned via Dependency Injection in main.
Services in this form usually represent Aggregates that in the end are meant to represent a cluster of domain objects to be treated as a single unit. In future iterations of this code we will see how that comes in to place when Task also include other things like SubTasks
and Categories
.
Parting words
Implementing services is useful to indicate interactions between domain objects and some external dependencies, like persistence layers. One of the difficult parts about implementing services is to know when those should be implemented, to determine that we should think about those cases where our business has specific processes that should be modeled a single unit.
Although in our "To Do Project" we are using Task
as the service name what this means is not the database table or model task but rather the process of interacting with Tasks and other entities, and all of that being handled as a single thing.
When defining services think in those terms, ask the question "What's the business process we are trying to model and what entities should be involved?" Answering that question should give you the name of the process we can model as a service.
Keep it up. I will talk to you next time.
Top comments (0)