Microservices in Practice: Architectural Decisions within the scope of Architectural Pattern Languages (ludotheca-share-mesh)

GitHub Repo: https://github.com/marco13-moo/ludotheca-share-mesh
Architectural Pattern Language
The microservice architecture pattern language was chosen in order to guide development of the resource-sharing system in order to meet the architectural decisions and characteristics of microservices [3:23]. This language is divided into 3 pattern layers being:
infrastructure
application infrastructure
application
Each layer comprises a multitude of groups and further sub-patterns [3:24]. Furthermore, an additional layer, the communication patterns layer, forms part of both the infrastructure and application infrastructure layers. Illustration of these layers may be found in appendix C.
Architectural Decisions Made/Design Principles Chosen within the Context of the Architectural Pattern Language
Application Patterns
Decomposition By Sub-Domain Pattern
Resolves decomposition issues
- Decomposition by subdomain is the process of scoping services in accordance to Evan's domain-driven model. [3:23] This entails defining the scope of a domain model, known as a bounded context. Once defined, the domain can be divided into sub-domains. Such a process may be recursively executed till a specified level of granularity is reached.
Database Per Service Pattern
Resolves issues in achieving the loose-coupling of microservices
- Each service was provided with its own datastore. This illustrates loose-coupling of each service as well adherence to API-only communication and services owning their own data. [3:12].
Saga Pattern - Orchestration vs. Choreography
Resolves data consistency maintenance
Orchestration Saga Pattern was chosen.
If there is a presence of transactions that span multiple services owning their own database- the saga pattern may be used in order to maintain data consistency across microservices [3:111].
The choreography-based saga pattern is instantiated with a sequence of local transactions co-ordinated via asynchronous messaging.
The orchestration-based saga entails controlling microservice elements in a coordinated manner.
Testing
- Unit testing was integrated as sociable test units in order to test both the microservice's internal service subdomain, it's dependencies as well as it's relation to the repository subdomain [3:308] .
Application Infrastructure Patterns
Cross-cutting Concerns Pattern
The resource-sharing system implements a microservices chassis pattern.
The microservices chassis pattern is defined as a collection framework that enables rapid microservice construction as well as cross-cutting concerns such as health checks and externalized configurations [3:379].
This pattern and framework suite was used as the Spring family adheres to the objective criteria of using a conservative technology stack whilst being familiar to students taking software architecture and engineering.
Security Pattern
Although security is often considered an architectural characteristic for microservices, the environmental scope that this specific set of microservices will be used in, does not require the security characteristic to be of high priority.
As simplicity and minimization of dependencies was favoured for this project, items that adhere to the security pattern such as access tokens were not implemented. As this system is based on teaching core microservice principles while providing as minimal configuration overheads as possible. Authentication mechanisms would be deemed detrimental to this criteria.
Observability Patterns
Provides aid for application monitoring in production environments
Health Check API Pattern
- This provides an additional diagnostic tool for the microservices architecture whilst running in production, allowing developers to observe if a microservice has failed unexpectedly or is taking a while to deploy.
Log Aggregation Pattern
- Log aggregation pattern refers to the aggregation of logs within a searchable and centralized database [3:368].
Distributed Tracing Pattern
- The distributed tracing pattern refers to each external request being recorded by a central server as a flow from one service to the next via a uniquely assigned ID [3].
Application Metrics Pattern
- The application metrics pattern provides services to report metrics to a centralized server for alerting, aggregation and visualization [3].
Client-Side Service Discovery Pattern
Resolves discovery of services on the network.
Provides services to reference URLs as opposed to dynamically changing IP addresses.
Netflix Eureka adheres to an application level service discovery pattern suite, being a combination of both:
The self-registration pattern, where an instance of a service registers itself with the service registry [3:82],
A client-side discovery pattern, where the service client retrieves available service instances on the network and load-balances across them [3:83].
Infrastructure Patterns
Server-Side/Platform Provided Service Discovery Pattern
Resolves discovery of microservices on the network.
Provides services to call instances belonging to the specified microservice name as opposed to a specific singular microservice instance via an IP address.
Prominent examples include Kubernetes Native Service Discovery.
Deployment Patterns
Each service was deployed according to the Service as a Container pattern for production runs.
Microservices are deployed as image containers into production, achieving fault isolation.
Each microservice instance belongs to their own container.
Communications Patterns
Transactional Messaging Pattern
A transactional messaging pattern was not implemented.
Transaction messaging is needed when a service requires message publishing as part of a transaction that updates the database [3:97].
As communication between all services are of a synchronous style, there is no need to apply such a pattern.
Communication Style
Inter-process communication is provided using the unified API set over HTTP in a RESTful manner. This aligns with the technology stack used within the CS5031 module.
All communication is based upon a synchronous style as it eliminates the need for an additional message broker.
It further provides students with immediate feedback via HTTP response codes or human-readable JSON payloads, helping aid the exploration and development of microservices [3:67].
Reliability - Circuit Breaker Pattern
- A circuit breaker pattern is a remote procedure invocation proxy, rejecting invocations for a timeout period after a set of consecutive failures exceeds a specified threshold [3:78].
External API pattern
Allows an entry-point into the architecture, minimizing the port numbers and IP addresses needed to interact with the application.
Allows for faster public-facing API redesign.
It further provides for authentication ability for the application, reducing evolution costs if authentication is chosen to be added in the future [3:259].
The Spring Cloud API gateway allows for an additional API composition pattern, whereby one client request via the gateway may invoke multiple API calls to a variety of microservices through the use of request handlers.
Design Decisions Outside Architectural Pattern Language Scope
Kubernetes
- Kubernetes was used as a container orchestration tool to automate the deployment and management of the microservices application. For further reading about Kubernetes infrastructure, please see appendix D.
API-First Design
The unified API contract was first specified following service decomposition.
The application adheres only to a RESTful API contract.
The following design principles were followed [7]:
Acceptance/Response in JSON
Gracefully handle errors by returning standard HTTP status codes
Nouns were used to identify the microservice being called in endpoint paths. (ie, GET /book/year/1997 gets all books published in 1997)
Implementation
Implementation of Architectural Pattern Language Application Patterns
Decomposition By Sub-Domain Pattern
In scope of this project, decomposition by sub-domain was executed as follows: A family of systems was scoped to a resource-sharing system domain.
The resource-sharing domain was decomposed into the following subdomains:
A member subdomain
A book subdomain
A lending subdomain
Each subdomain was mapped to a microservice, adhering to the architectural decision that a microservice is to be a "standalone, independently deployable software component that implements some useful functionality" [3:41].
Thus, the resource sharing system comprises of the following core microservices responsible for the application's business capabilities:
A member microservice - responsible for managing members
A book microservice - responsible for books available/unavailable
A lending microservice - responsible for loan functionality
Once the microservices were defined, each microservice then provided it's own domain model [3:150].
Each domain included the following tactical patterns acting as building blocks for the domain model. Richardson notes each pattern as being defined as a role a class/interface plays in the domain model and provides a definition of the class's characteristics [3:150]:
Entity - "An object that has a persistent identity."
Service - "An object that implements business logic that doesn't belong in an Entity."
Repository - "An object that provides access to persistent entities and encapsulates the mechanism for accessing the database."
Each building block pattern in the final decomposition is instantiated with their respective Java packages per microservice. Each microservice is instantiated as an independent Spring Boot application.
Database Per Service Pattern
To illustrate that the database per service pattern adhered to the use of independent datastores- the resource-sharing system has the ability for each service to connect to a unique datastore of the team's choice.
For this project, the following choices were implemented:
A local MySQL server
A cloud based MySQL server via GCP's CloudSQL service
H2, an in-memory datastore (aimed for rapid deployment/testing)
For example, each team responsible for one of the three services may choose one of the three datastores that another team is not using.
Saga Pattern - Orchestration
As the book-lending system is based on synchronous RESTful communication, orchestration is prevalent across the architecture such as the checkOutLoan.
The checkOutLoan controller will invoke the check out loan business logic, whereby each service involved in the transaction is called consecutively, if the callee service does not provide the correct response, the transaction is rolled back.
Testing
Integration tests were performed using MockMVC, a familiar testing framework for software engineering students.
The integration tests tested the controller subdomain of each core microservice with the requirement that all other core microservices and the Eureka service registry is running. This entails that the I/O of microservice calls provides the correct response and is of acceptable latency.
It also illustrates to students where inter-process communication occurs within the microservices architecture [3].
Application Infrastructure Patterns
Cross-cutting Concerns Pattern
The Spring Boot and Spring Cloud framework as a microservice chassis was used in order to hoist business logic, microservice functionality, communication, the API gateway as well as health checks and service discovery via Spring Boot actuator and Spring Cloud Netflix Eureka.
Observability Patterns
Health Check API Pattern
The Spring Boot actuator library was used in order to expose a health check API endpoint via GET /actuator/health [3:366] for each Spring Boot microservice.
This returns the health of the microservice, allowing developers to check the health status of each microservice independent of which deployment platform is chosen.
Log Aggregation Pattern
This was implemented through the use of Cloud Logging via GCP's GKE log explorer. Although not a traditional relational database, logging data has the ability to persist after cluster termination as log container buckets.
Through the use of a cloud provider's logging service, one is able to maintain a conservative technology stack that reduces evolution costs as logging services are updated continuously through the cloud provider rather than pushing vigilance to the developer.
Furthermore, developers have a choice of deploying the microservices architecture on other cloud provider platforms to leverage other logging offerings. As Fowler defines microservices to adhere to using "fully automated deployment machinery" [4] as well as microservices adhering to a bare minimum of centralized management [4]- hoisting microservices architecture within cloud infrastructure greatly complements the key characteristics mentioned above.
Distributed Tracing Pattern
- As this added Google Cloud specific dependencies to the architecture thereby giving rise to additional complexity, this pattern was not implemented.
Application Metrics Pattern
As the microservices was deployed via Google Cloud Platform, it provided the benefit of not needing any dependencies to apply this pattern.
This ensures a conservative technology stack. For example, GKE logs explorer provides cluster and container log visualization via the logs dashboard.
Developers may also define log metrics to retrieve an aggregation of logs that match the filter criteria.
Logs Explorer also provides a friendly GUI for log searching via log fields and values. Developers may also define specified log queries to quickly access certain logging criteria.
Client-Side Service Discovery Pattern
With adherence to developing microservices in the scope of a teaching framework, client-side service discovery via Spring Cloud Netflix Eureka was first implemented.
This provides service discovery on a local machine.
Furthermore, the Eureka service registry provides a graphical web page interface, displaying all microservices registered on the network. This provides a unique learning experience for software engineering and architecture students to view the microsservices' location and their status.
Can be leveraged either in the cloud or a local machine.
Infrastructure Patterns
Server-Side/Platform Provided Discovery Pattern
As the objectives required another architectural pattern to be applied, the server-side service discovery pattern was explored and implemented [3:83].
In turn, it proved to be far more successful in providing the desired architectural characteristics required for microservices, specifically with regards to allowing each service to be independently deployed and scaled through automated deployment infrastructure.
The platform-provided service discovery mechanism chosen and implemented was the Kubernetes native service discovery mechanism hoisted on Google Kubernetes Engine.
Deployment Patterns
The resource-sharing system was first deployed on a local machine in order to fulfill a minimum viable product.
In accordance with the primary objectives, the microservices were then deployed upon infrastructure. The infrastructure chosen was KaaS, Kubernetes-as-a-Service.
This achieves the architectural pattern as each container provides their own isolated process and file system whilst minimizing computing overhead opposed to using entire virtual machines per microservice instance.
Independent deployability and scalability during production was successfully achieved using Kubernetes native service discovery.
Using Eureka service discovery, the containerised service images had to be deployed together in order to achieve interprocess communication. These services do have the ability to be independently scaled and deployed using Eureka service discovery, but will not achieve inter-service communication between one another.
Be it through use of Eureka service discovery or Kubernetes native service discovery, the services were successfully wrapped into Docker containers using Jib and pushed to the Google Container registry.
Communications Patterns
Communication Style
- Implemented via @RestController annotation and Spring REST controller mapping in the controller subdomain.
Reliability - Circuit Breaker Pattern
The lack of a circuit breaker pattern was chosen as it's additional complexity and use of the deprecated Netflix Hystrix library do not provide the benefits of such a pattern implementation given the project scope and system use.
During student evaluation, response codes proved ample for debuggability at a teaching scope.
External API Pattern
The API gateway pattern was implemented as an external entry point to the book lending system.
Using Spring Cloud gateway, all external clients have their API requests load-balanced and routed across the various microservices living within the internal network.
The API gateway is able to run as a stand-alone service using Kubernetes service discovery or may be coupled with Eureka service discovery.
Implementation Outside Scope of Architectural Pattern Language
Implementation of Kubernetes
Deployment of the Eureka based book-lending system on Google Kubernetes Engine:
eurekaDeployAndService YAML file:
Defines a Kubernetes service of the LoadBalancer type.
Provides an external IP address (using GCP load-balancer) for users to access the Eureka GUI.
The Kubernetes service of Eureka, "srservice", allows the srservice domain name to be referenced during microservice push-based externalized configuration.
The YAML file further defines the deployment to be a single pod with a replica set of
1. This may be changed during production or within the YAML configuration file.
- The pod runs a single container being the Eureka service registry.
4ServicesDeployAndService YAML file:
Defines the 4 microservices into a single Kubernetes service instance, "microservice-chassis-depl-name-service" of LoadBalancer type.
Provided a single external IP address for users to access the end-points of the core microservices.
Only port 8484 assigned with the API gateway is exposed externally.
The pod runs 4 containers being the gateway microservice, member microservice, lending microservice and book microservice.
mysqlservice YAML:
Defines a mysql Kubernetes service, this allows a mysqlservice domain name to be referenced during microservice push-based externalized configuration.
This file allows for the microservices to externally connect to it's dedicated database hosted on cloudSQL.
Deployment of the book-lending system leveraging native Kubernetes Service Discovery on Google Kubernetes Engine:
bookDeployAndService YAML:
Defines a Kubernetes service of the nodePort type.
The Kubernetes service, "book-service", allows the book-service domain name to be referenced during microservice inter-process communication and Spring Cloud API gateway load-balancing.
The YAML file further defines the deployment to be of a single pod with replica set at
1. This may be changed during production or within the YAML configuration phase.
The pod runs a single container being the book service from the kubeDiscoveryBranch.
The above bookDeployAndService YAML file follows the same template for the member and lending microservices.
All IP addresses for these core microservices are defined across the internal Kubernetes cluster network and are not accessible to the outside world.
gatewayDeployAndService YAML:
Defines the Spring Cloud API gateway into a single Kubernetes service instance, "gateway-service" of LoadBalancer type.
Provides a single external IP address for users to access the API end-points of the core microservices.
The YAML file further defines the deployment to be of a single pod with replica set at 1.
This may be changed during production or within the YAML configuration phase. The pod runs 1 container being the gateway microservice.
mysqlservice YAML:
Defines a mysql Kubernetes service, this allows a mysqlservice domain name to be referenced for push-based externalized configuration.
This choice over a sidecar pattern was decided as it provides developers to externally map IP addresses of database servers that are not cloud hosted.
Please see Appendix D for overview on Kubernetes.
API Implementation
API per microservice was defined under the controller subdomain. Instantiated via the @RestController Spring annotation to auto-serialize return entities in HTTP responses, simplifying the controller implementation.
@GetMapping etc. Spring controllers used to map operations to corresponding REST calls.
Microservice Database Schema Creation
- Spring Data JPA auto-generated the database schema per microservice, a clear illustration of architectural hoisting.
Subscribe to my newsletter
Read articles from Marco directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Marco
Marco
Senior DevOps Engineer exploring the world of distributed systems