resource microservices overview - grecosoft/NetFusion GitHub Wiki
The following summaries the concepts and designs contained within the following Microsoft microservice reference:
Microservices and Docker containers: Architecture, Patterns and Development guidance
The majority of the guidance provided in this reference is on structuring code best suited for running as a microservice within Docker. Only .NET Core is discussed since it is best suited for creating Docker based microservices. The patterns discussed also work well for microservices hosted within IIS and are not dependent on Docker. While there are many benefits provided by running within Docker, only the structure and best practices for designing a microservice will be discussed. The Embedded team has already started configuring and deploying Docker so it will not be repeated here.
-
Since a business solution is partitioned into a set of cohesive and loosely coupled microservice components, specific parts of the solution can be scaled without having to scale the entire solution.
-
A microservice belonging to a bigger business solution can be updated and deployed independently of other microservices.
-
The size of a given Microservice should be based on a cohesive set of functionally requiring few or no dependencies on other microservices.
-
Allows developers to be more productive since they can focus on the features provided by a specific microservice without having to work in a large monolithic code-base.
-
Each microservice can be independently tested.
-
While common architectures and shared components are important to keep structures between microservices consistent, a microservice only needs to reference components specific to its implementation.
-
A microservice should own its related domain-model, domain-logic, and persisted data. These items should only be directly accessible by the owning microservice. Microservices should never integrate by accessing each others persisted data directly or by sharing domain models and logic contained in common libraries.
-
While the communication between contexts/microservices should be limited it will be needed at times.
-
If another service needs access, it should be done via REST calls or better yet by publishing and subscribing to integration domain-events passed between the two microservices.
-
When there is a needed dependency, favor asynchronous integration over synchronous HTTP calls.
-
It is also suggested that if a given microservice needs to query data from another, to which it appends data specific to its microservice, the "materialized view" pattern can be used. Basically the microservice needing the data can subscribe to domain-events, published by the owning microservice, and store a copy of the information tailored to its needs.
-
It is suggested to not share any common types between services and to favor different types that serialize/deserialize to compatible formats. For example, when a microservice subscribes to a queue, the microservice should create a type matching the serialized format of the published domain event. If types are shared, a simple change can break all other microservices and prevents microservices from being deployed independently.
A software solution comprises of multiple independent microservices each scoped to the implementation of a specific context.
The above shows multiple microservices being deployed to a Docker Host. The next section shows the internal structure of a microservice using patterns common to microservice based architectures,
The following are closely related structural patterns for organizing the components within a microservice:
Bound Context DDD deals with large models by dividing them into different Bounded Contexts and being explicit about their interrelationships.
Onion Architecture When utilizing such designs as Inversion of Control implemented via Dependency Injection this pattern allows for reduced coupling between components. This also makes the dependencies between the components well defined. The basic idea is that the domain is at the core and all other outer layers support its functioning. Also, all dependencies point from outer to inner layers. Since the domain is the innermost layer, it has no dependencies to the other layers.
- Understanding Onion Architecture
- Onion Architecture is Interesting
- Onion Architecture In ASP.NET Core MVC
Port and Adapter This is closely related to the Onion Architecture and used to classify and separate application components that process inputs (Ports) and communicate with external systems (Adapters). These two components are part of the application layer. Adapters are a type of anti-corruption layer used to make an external system confirm to the calling application's structure and needs.
- Improve Your Software Architecture with Ports and Adapters
- Ports-And-Adapters / Hexagonal Architecture
The following diagram shows how a microsevice context is structured when using the above patterns:
Assembly | Description |
---|---|
NetFusion.Bootstrap | Implements a well defined bootstrap process allowing the components contained within each layer to be easily composed into an application. The bootstrap process also initializes Microsoft's logging and configuration extensions allowing ASP.NET Core and Console applications to be identically bootstrapped. |
NetFusion.Messaging | Provides the implementation of dispatching queries, sending commands, and publishing domain events. This allows the above layers to be loosely coupled. |
The following describes each layer and the commonly contained components. An important constraint of an Onion Architecture is that only a outer layer can communicate to its inner defined layers. It should be noted that the Domain is contained in the center and therefore has no dependencies on any other layer. This is important to prevent the Domain layer from becoming littered with technical concerns resulting in a domain-model that better models the business and is easier understand and unit-test.
Host
1 - Host creates Commands and Query defined within the API. The responses from the host are Resources defined within the API layer. The host is most often a ASP.NET Core WebApi process having controllers that send Commands and dispatch Queries to the Application layer.
2 - The response of a Command or Query may be a Domain-Entity which is mapped to its corresponding Resource.
Assembly | Description |
---|---|
NetFusion.Rest.Common | Small assembly containing common REST types such as RelationType. |
NetFusion.Rest.Server | Discovers relation mappings specifying what HAL links should be associated with a returned resource. This assembly also contains the base mapping class based on a type-safe and fluent API. Also contained within this assembly is the HAL media formatter that checks for returned resources and adds their associated HAL links. |
NetFusion.Web.Mvc | Contains extensions for ASP.NET Core and code that stores all MVC Routes for use by the REST/HAL formatter when looking up template based URLS (i.e. \api\orders\{orderId}\status) |
API
3 - The Api layer defines Commands and Queries that are handled by components defined within the Application layer. The Api layer also define Resources that define the contract of the services exposed by the Host layer. When a Command is executed, it may return a Domain-Entity that the Api or Host layers map to its corresponding Resource.
Assembly | Description |
---|---|
NetFusion.Messaging.Types | Assembly containing the contracts for Queries, Commands, and domain-events. |
NetFusion.Rest.Resources | Assembly containing the classes used to represent HAL based resources. |
Application
4 - Ports, defined within the Application layer, handle received Commands which may have an associated Resource or Model defined within the API component. The Resource or Model is usually populated from a received HTTP request. When a Port receives a Resource, it is mapped to its corresponding Domain-Entity defined in the domain layer. Ports also handle received Queries defined within the Api layer to return needed state.
5 - In addition to mapping Resources to Domain-Entities, Ports also implement a business need by delegating to one or more Domain-Entities. They also consume Service and Repository contracts defined with the Domain layer. The Repositories return instances of Domain-Entities which are passed to business services to execute business logic. The business logic contained within services should be logic pertaining to multiple domain-entities or logic that does not belong specifically to any one Domain-Entity. When possible, most the business logic should be within the Domain-Entities.
Assembly | Description |
---|---|
NetFusion.Domain | Contains common base domain definitions. |
NetFusion.Domain.Patterns | Contains some common domain patterns to help structure the application. |
NetFusion.Messaging | Contains the contracts and attributes used to specify a component's methods as a Query, Command, or Domain-Event consumer. |
Domain
The Domain layer models business concepts defined within the context being implemented by the service. This layer has no dependencies on any other layer. This is important since it should model the business process and not be littered with technical concerns. Also defined within this layer are the contracts for Services and Repositories. These contracts are implemented by the Application and Infrastructure layers and consumed from components defined within the Application layer to implement business work flows. The Domain layer also defines Domain-Events used to notify other components of an occurrence important to the business domain.
Assembly | Description |
---|---|
NetFusion.Domain | Contains common base domain definitions. |
NetFusion.Domain.Patterns | Contains some common domain patterns to help structure the application. |
Infrastructure
The Infrastructure layer contains implementations based on specific technologies for which all other layers should have no knowledge. This includes implementations of the Repository contracts defined within the Domain layer and implementations for Adapter contracts defined within the Application layer.
Assembly | Description |
---|---|
NetFusion.EntityFramework | Simple plug-in that discovers all Entity Framework database and mappings. Also responsible for creating a context for a specific configured database when injected into a repository. |
NetFusion.MongoDB | Simple plug-in that discovers all MongoDB database and mappings. Also responsible for creating the client for a specific configured database when injected into a repository. |
NetFusion.RabbitMQ | Contains a bootstrap process extending the base messaging by discovering all exchange definitions used to create the corresponding RabbitMQ exchanges on the server. The bootstrap process also looks for all services with message handlers for domain-events with defined exchanges and automatically subscribes the method to be called when a message is received. |
NetFusion.Rest.Client | Simple client delegating to the Microsoft HttpClient extended with methods to allow REST/HAL based services to be easily called and navigation of the returned resources links. |
NetFusion.Rest.Config | Optional plug-in that initializes the Http Rest clients based on configurations found within the application's configuration settings. |
NetFusion.Settings | Provides simple extension to Microsoft's configuration extensions so settings can be loaded and injected into dependent components. |
Below are the common patterns related to DDD and micro-architectures. Not all of the patterns are needed for a given context implementation but some of the patterns such as CQRS, Command, Query, Domain Entity, and Domain Events are key to structuring the code contained within a given context.
Repository Pattern All database centric code should be encapsulated using the repository pattern external to domain-entities.
Aggregate The aggregate DDD pattern is based around the concept of a set of closely related domain-entities that must:
- Remain consistent as a unit.
- Be validated as a unit and not committed if invalid.
- Provides a higher level programming interface used to maintain the aggregated domain-entities.
- Allows for easier understanding of the code since aggregates form the top most level of the application’s design.
- Aggregates should be kept decoupled and coordinated by application services and the unit-of-work. For example, logic for creating a new order can delegate to one or more aggregates and domain-entities that are saved within the context of an unit-of-work.
Unit-Of-Work The unit of work is created for a given request context and contains references to all aggregates updated to perform a business scenario carried out by the request. The unit-fo-work is responsible for assuring all enlisted aggregates are valid and consistent.
Domain Entit Models a concept found within the domain having an identity value. Two entities with the same state but different identity values are considered to be different.
Value Type Models a concept found within the domain absent of any identity value. Two value types are the same if all properties used to identity the type are the same. Value types should also be non-mutable. Any changes made to a value type should return a new updated instance. This helps reduce bugs since a values type's state cannot be changed.
CQRS (Command Query Responsibility Segregation) Allows for the separation of actions that modify a system’s data from querying. This allows for reads to be streamlined without the overhead of additional actions necessary during the processing of changes to an application’s state.
-
Command All application state changes are initiated by a command and processed by an application component. The command is often initiated by the Web API controller. The command encapsulates properties describing the action to be taken. The properties of the command are usually populated from the HTTP request body and route parameters. Commands are handled by application ports that are decoupled from the publisher.
-
Query Used to represent a query for the current state of the application. The source of the query may or may not be the directly persisted domain-entity state. For example, a query may be based on a database view that aggregates one or more underlying tables. There are other patterns such as the Materialized View that can create the data to be queried by subscribing to other service’s published domain-events.
-
Applying simplified CQRS and DDD patterns in a micro-service
Domain Event A simple class representing a message that is published to notify other internal and external service components about an important change in application state. Usually the processing of a command results in the publishing of one or more domain-events. The publisher of a domain event is decoupled from the consumer.
REST/HAL More of a standard than a pattern but important. Returning service responses based on a known standard allows for easy consumption by other applications and services. While not the only standard, this is the most common for communicating with web applications and mobile devices. This standard response format can also be consumed easily by other server-side components such as executable processes and back-end services.
- HATEOAS Driven REST APIs
- What is REST
- RESTful Web Services: A Tutorial
- HAL - Hypertext Application Language
Service Gateway This pattern acts as a central point for a request delegating to one or more internal and external services aggregating the received data. While this should not be overused, it helps simplify an application API by providing a response composed of several sources of data returned as a single unit. This can also be used to reduce the number of interdependent client calls that would be needed to receive the same data.