Skip to content

Commit

Permalink
CODE RUB: Services 2.0 Grammer Check
Browse files Browse the repository at this point in the history
  • Loading branch information
glhays committed Apr 17, 2024
1 parent ef55f40 commit 5f05779
Showing 1 changed file with 30 additions and 27 deletions.
57 changes: 30 additions & 27 deletions 2. Services/2. Services.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

## 2.0 Introduction

Services, in general, are the containers of all the business logic in any given software - they are the core component of any system and the main component that makes one system different from another.
Services, in general, are the containers of all the business logic in softwarethey are the core component of any system and the main component that makes one system different from another.

Our main goal with services is to keep them agnostic from specific technologies or external dependencies.

Any business layer is more compliant with The Standard if it can plug into any other dependencies and exposure technologies with the least amount of integration effort.
Any business layer is more compliant with The Standard if it can plug into other dependencies and exposure technologies with the least integration effort.

### 2.0.0 Services Operations

Expand All @@ -22,21 +22,21 @@ Let's talk about these categories.

#### 2.0.0.0 Validations

Validations ensure that incoming or outgoing data match a particular set of rules: structural, logical, or external validations, in that exact order of priority. We will go into details about this in the upcoming sections.
Validations ensure that incoming or outgoing data match a particular set of rules, such as structural, logical, or external validations, in that exact order of priority. We will discuss this in detail in the upcoming sections.

#### 2.0.0.1 Processing

Processing mainly focuses on the flow-control, mapping, and computation to satisfy a business need - the processing operations distinguish one service from another and, in general, one software from another.
Processing mainly focuses on flow control, mapping, and computation to satisfy a business needthe processing operations distinguish one service from another and, in general, one piece of software from another.

#### 2.0.0.2 Integration

Finally, the integration process focuses on retrieving or pushing data from or to any integrated system dependencies.

We will discuss these aspects in detail in the upcoming chapter. The main thing to understand about services is that the design is to be pluggable and configurable to easily integrate with any technology from a dependency standpoint and easy to plug into any exposure functionality from an API perspective.
We will discuss these aspects in detail in the upcoming chapter. The main thing to understand about services is that their design is to be pluggable and configurable, allowing them to easily integrate with any technology from a dependency standpoint and easily plug into any exposure functionality from an API perspective.

### 2.0.1 Services Types

Services have several types based on their disposition in any given architecture. They fall under three main categories: validators, orchestrators, and aggregators.
Services are classified into several types based on their disposition in any given architecture. They fall into three main categories: validators, orchestrators, and aggregators.

<br />
<p align=center>
Expand All @@ -56,15 +56,18 @@ Orchestrator services are the core of the business logic layer. They can be proc

Orchestrator services mainly focus on combining multiple primitive operations or multiple high-order business logic operations to achieve an even higher goal.

Orchestrator services are the decision makers within any architecture, the owners of the flow control in any system, and the main component that makes one application or software different from the other.
Orchestrator services are:
The decision-makers within any architecture.
The owners of the flow control in any system.
The main component that makes one application or software different from the other.

We intentionally design Orchestrator services to be longer-lived than any other type of service in the system.

#### 2.0.1.2 Aggregators

Aggregator services' primary 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.
The primary responsibility of the aggregator services 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 are the gatekeepers of the business logic layer. They ensure the data exposure components (like API controllers) interact with only one point of contact to interact with the rest of the system.

Aggregators, in general, don't care about the order in which they call the operations that are attached to them. Still, it is sometimes necessary to execute a particular operation, such as creating a student record before assigning a library card.

Expand All @@ -78,15 +81,15 @@ These rules ensure the system's overall readability, maintainability, and config

#### 2.0.2.0 Do or Delegate

Every service should either do or delegate the work but not both.
Every service should either do 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 instance, a processing service should delegate the work of persisting data to a foundation service rather than try to do that work itself.

#### 2.0.2.1 Two-Three (Florance Pattern)

For Orchestrator services, their dependencies of services (not brokers) should be limited to two or three, but not one or not four or more.
For Orchestrator services, the dependencies of services (not brokers) should be limited to two or three, not one, four, or more.

If an Orchestrator depends only on one service, then it violates the definition of orchestration which is the combination of multiple operations from different sources to achieve a higher order of business logic.
Suppose an Orchestrator depends only on one service. In that case, it violates the definition of orchestration, which is the combination of multiple operations from different sources to achieve a higher order of business logic.

###### This pattern violates Florance Pattern

Expand All @@ -106,45 +109,45 @@ If an Orchestrator depends only on one service, then it violates the definition

The Florance pattern also ensures the balance and symmetry of the overall architecture.

For instance, you can't orchestrate between a foundation and a processing service. It causes a form of unbalance in your architecture and difficulty when trying to combine one unified statement with the language each service speaks based on their level and type.
For instance, you can't orchestrate between a foundation and a processing service. This causes an imbalance in your architecture and difficulty when trying to combine one unified statement with the language each service speaks based on its level and type.

The aggregators are the only types of services allowed to violate this rule, where the combination and the order of services or their calls don't have any real impact.

We will discuss the Florance pattern in detail in the upcoming sections of The Standard.

#### 2.0.2.2 Single Exposure Point

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.
API controllers, UI components, or any other form of system data exposure should have one single point of contact with the business logic layer.

For instance, an API endpoint that offers endpoints for persisting and retrieving student data should not have multiple integrations with multiple services but rather one service that provides all of these features.
For instance, an API endpoint that offers endpoints for persisting and retrieving student data should not have multiple integrations with multiple services but one service that provides all these features.

Sometimes, a single orchestration, coordination, or management service does not offer everything related to a particular entity. An aggregator service combines all of these features into one service ready to be integrated with an exposure technology.
Sometimes, a single orchestration, coordination, or management service does not offer everything related to a particular entity. An aggregator service combines all these features into one service that is ready to be integrated with exposure technology.

#### 2.0.2.3 Same or Primitives I/O Model

All services must maintain a single contract regarding their return and input types, except if they are primitives.

For instance, a service that provides operations for an entity type `Student` - should not return from any of its methods from 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.
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.

A similar rule applies for input parameters - any service may receive an input parameter of the same contract, a virtual aggregation contract, or a primitive type but not any other contract.

This rule focuses the responsibility of a service on a single entity and all its related operations.

When a service returns a different contract, it violates its naming convention like a `StudentOrchestrationService` returning `List<Teacher>` - and it starts falling into the trap of being called by other services from entirely different data pipelines.

For primitive input parameters, if they belong to a different entity model that is not necessarily a reference to the primary entity, it begs the question of orchestrating between two processing or foundation services to maintain a unified model without breaking the pure-contracting rule.
If primitive input parameters belong to a different entity model that is not necessarily a reference to the primary entity, it begs the question of orchestrating between two processing or foundation services to maintain a unified model without breaking the pure-contracting rule.

Suppose the combination of multiple different contracts in an orchestration service is required. In that case, a new unified virtual model indicates the need for a new unique contract for the orchestration service with mappings implemented underneath on the concrete level of that service to maintain compatibility and integration safety.
Suppose an orchestration service requires a combination of multiple different contracts. In that case, a new unified virtual model indicates the need for a new unique contract for the orchestration service, with mappings implemented underneath on the concrete level of that service to maintain compatibility and integration safety.

#### 2.0.2.4 Every Service for Itself

Every service is responsible for validating its inputs and outputs. Do not rely on services up or downstream to validate your data.
Every service is responsible for validating its inputs and outputs. Do not rely on services upstream 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 service is not be affected if down or upstream services decided to pass on their validations for any reason.
This is a defensive programming mechanism to ensure that if implementations are swapped behind contracts, the responsibility of any given service is not affected if downstream or upstream services decide to pass on their validations for any reason.

Within any monolithic, microservice, or serverless architecture-based system, every service is designed so that it can split off from the system at some point and become the last point of contact before integrating with some external resource broker.
Within any monolithic, microservice, or serverless architecture-based system, every service is designed to split off from 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 map parts of an input `Student` model into a `LibraryCard` model. Here's a visual of the models:

Expand Down Expand Up @@ -198,11 +201,11 @@ private LibraryCard MapToLibraryCard(Student student)
}
```

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 its id is in good shape before proceeding with the orchestration process.
As you can see above, a valid student id is required to map to a `LibraryCard successfully`. Since the mapping is the orchestrator's responsibility, we must ensure that the input student and its id are in good shape before proceeding with the orchestration process.

#### 2.0.2.5 Flow Forward
Services cannot call services at the same level. For instance, Foundation Services cannot call other Foundation Services, and Orchestration Services cannot call other Orchestration Services from the same level.
This principle is called a Flow-Forward - as the illustation shows:
This principle is called a Flow-Forward - as the illustration shows:


<br />
Expand Down Expand Up @@ -234,6 +237,6 @@ public async ValueTask<Student> ModifyStudentAsync(Student student)
}
```

In a Foundation Service example above, we cannot call `RetriveStudentByIdAsync` in a `public` method from another `public` method such as `ModifyStudentAsync`. You will see that both methods called the exact same method from a lower dependency like a `StorageBroker` full independent of one another.
In the Foundation Service example above, we cannot call `RetriveStudentByIdAsync` in a `public` method from another `public` method such as `ModifyStudentAsync`. You will see that both methods call the exact same method from a lower dependency, like a `StorageBroker`, fully independent of one another.

While this may seem redundant, the reason for this is that `public` APIs, contracts or otherwise are destined to be deprecated at some point in it's lifetime. It may also be changed completely from an implementation standpoint. If a `public` API depended on another `public` API at the same level the deprecation of one will cause a cascading effect on all others. That's a symptom of Chaotic design which The Standard strongly prohibits.
While this may seem redundant, the reason for this is that `public` APIs, contracts, or otherwise, are destined to be deprecated at some point in their lifetime. They may also be changed completely from an implementation standpoint. If a `public` API depended on another `public` API at the same level, the deprecation of one will cause a cascading effect on all others. That's a symptom of Chaotic design, which The Standard strongly prohibits.

0 comments on commit 5f05779

Please sign in to comment.