Services
Last updated
Last updated
Services are the ==containers of all business logic== in software
They are the core component of any system and the main component that makes one system different from another.
Services Goal is to:
==be agnostic to specific technologies or external dependencies.==
Services are defined by three main categories of operations:
Validation
Processing
Integration
Validating input and output data by matching a set of rules:
Structural validation
Logical validation
External validation
==Core Business Logic==, la raison d'être of a particular service:
Flow-control
Mapping
Computation to satisfy a business need
==Retrieving or pushing data== from or to any integrated system dependencies.
Services should be built with the intent of being pluggable and configurable. They are easily integrated with any technology from a dependency standpoint. They are easily plugged into any exposure functionality from an API perspective.
Services have several types based on where they stand in any given architecture. They fall under three main categories, which are: validators, orchestrators and aggregators.
Validator services are mainly the broker-neighboring services or foundation services.
These services' main responsibility is to add a validation layer on top of the existing primitive operations such as the CRUD operations to ensure incoming and outgoing data is validated structurally, logically and externally before sending the data in or out of the system.
Orchestrator services are the core of the business logic layer, they can be processors, orchestrators, coordinators or management services depending on the type of their dependencies.
Orchestrator services mainly focuses on combining multiple primitive operations, or multiple high-order business logic operations to achieve an even higher goal.
Orchestrators services are the decision makers within any architecture, they are the owners of the flow-control in any system and they are the main component that makes one application or software different from the other.
Orchestrator services are also meant to be built and live longer than any other type of services in the system.
Aggregator services main responsibility is to tie the outcome of multiple processing, orchestration, coordination or management services to expose one single API for any given API controller, or UI component to interact with the rest of the system.
Aggregators are the gatekeepers of the business logic layer, they ensure the data exposure components (like API controllers) are interacting with only one point of contact to interact with the rest of the system.
Aggregators in general don't really care about the order in which they call the operations that is attached to them, but sometimes it becomes a necessity to execute a particular operation, such as creating a student record before assigning a library card to them.
We will discuss each and every type of these services in detail in the next chapters.
There are several rules that govern the overall architecture and design of services in any system.
These rules ensure the overall readability, maintainability, configurability of the system - in that particular order.
Every service should either do the work or delegate the work but not both.
For instance, a processing service should delegate the work of persisting data to a foundation service and not try to do that work by itself.
For Orchestrator services, their dependencies of services (not brokers) should be limited to 2 or 3 but not 1 and not 4 or more.
The dependency on one service denies the very definition of orchestration. that's because orchestration by definition is the combination between multiple different operations from different sources to achieve a higher order of business-logic.
This pattern violates Florance Pattern
This pattern follows the symmetry of the Pattern
The Florance pattern also ensures the balance and symmetry of the overall architecture as well.
For instance, you can't orchestrate between a foundation and a processing services, it causes a form of unbalance in your architecture, and an uneasy disturbance in trying to combine one unified statement with the language each service speaks based on their level and type.
The only type of services that is allowed to violate this rule are the aggregators, where the combination and the order of services or their calls doesn't have any real impact.
We will be discussing the Florance pattern a bit further in detail in the upcoming sections of The Standard.
API controllers, UI components or any other form of data exposure from the system should have one single point of contact with the business-logic layer.
For instance, an API endpoint that offer endpoints for persisting and retrieving student data, should not have multiple integrations with multiple services, but rather one service that offers all of these features.
Sometimes, a single orchestration, coordination or management service does not offer everything related to a particular entity, in which case an aggregator service is necessary to combine all of these features into one service ready to be integrated with by an exposure technology.
For all services, they have to maintain a single contract in terms of their return and input types, except if they were primitives.
For instance, a service that provides any kind of operations for an entity type Student
- should not return from any of it's methods any other entity type.
You may return an aggregation of the same entity whether it's custom or native such as List<Student>
or AggregatedStudents
models, or a primitive type like getting students count, or a boolean indicating whether a student exists or not but not any other non-primitive or non-aggregating contract.
For input parameters a similar rule applies - any service may receive an input parameter of the same contract or a virtual aggregation contract or a primitive type but not any other contract, that simply violates the rule.
This rule enforces the focus of any service to maintain it's responsibility on a single entity and all it's related operations.
Once a service returns a different contract, it simply violates it's own naming convention like a StudentOrchestrationService
returning List<Teacher>
- and it starts falling into the trap of being called by other services from a completely different data pipelines.
For primitive input parameters, if they belong to a different entity model, that is not necessarily a reference on the main entity, it begs the question to orchestrate between two processing or foundation services to maintain a unified model without break the pure-contracting rule.
If the combination between multiple different contracts in an orchestration service is required, then a new unified virtual model has to be the new unique contract for the orchestration service with mappings implemented underneath on the concrete level of that service to maintain compatibility and integration saftey.
Every service is responsible for validating it's inputs and outputs. you should not rely on services up or downstream to validate your data.
This is a defensive programming mechanism to ensure that in case of swapping implementations behind contracts, the responsibility of any given services wouldn't be affected if down or upstream services decided to pass on their validations for any reason.
Within any monolithic, microservice or serverless architecture-based system, every service is built with the intent that it would split off of the system at some point, and become the last point of contact before integrating with some external resource broker.
For instance, in the following architecture, services are mapping parts of an input Student
model into a LibraryCard
model, here's a visual of the models:
Student
LibraryCard
Now, assume that our orchestrator service StudentOrchestrationService
is ensuring every new student that gets registered will need to have a library card, so our logic may look as follows:
As you can see above, a valid student id is required to ensure a successful mapping to a LibraryCard
- and since the mapping is the orchestrator's responsibility, we are required to ensure that the input student and it's id is in good shape before proceeding with the orchestration process.