Microservices in Practice: Technical Evaluation of Patterns Applied within the Context of the Architectural Pattern Language (ludotheca-share-mesh)

GitHub Repo: https://github.com/marco13-moo/ludotheca-share-mesh
Application Patterns
Decomposition by Sub-domain Pattern
The decomposition by sub-domain pattern provided the framework needed to define the bounded-contexts of the system.
Through this pattern, the microservices characteristic, "componentization via services" was met [4].
Such a decomposition pattern further allowed for the characteristic that microservices should be "organized around business capabilities" [4].
Decomposing the system to be centered around business capabilities allows for cross-functional teams [4] as well as autonomous development and decentralized governance.
With the lending microservice being responsible for the checkout and return of items, the book and member microservices being responsible for book and member management- students within a software engineering team may be subdivided into smaller groups responsible for each service. Such decomposition promotes a full stack of skills to be used, with each team responsible for business logic, API design as well as persistence and project management.
But problems within the scope of granularity may arise. Architects and developers may believe that a microservice entails each service to be of miniscule proportions. Such a misconception may lead services to be too fine-grained. By defining bounded-contexts that are relative to a specific scope within the system, it provides guidance towards architecting microservices of healthy granularity and service boundaries [2].
If the sub-domain pattern is not a viable solution, an alternative pattern such as the decomposition by business capability pattern may be more suitable. This entails microservices to be defined around business capabilities, of which is then further decomposed into sub-capabilities [3:52]. Although such a pattern may be inefficient through the need for service combinations and expensive inter-process communication, the resulting architecture is often stable due to it's direct mapping to stable business capabilities [3:53].
Database Per Service Pattern
The database per service pattern allows for Fowler and Lewis's "decentralized data management" characteristic to be obtained [4].
It potentially provides polyglot persistence to be obtained across the system, allowing for developers to optimize data retrieval and operations specific to each microservice.
As the book-lending system is not designed to be used for big data analysis, JPA was chosen for object-relational mapping, scoping the datastore to be of a relational type.
The pattern further emphasizes loose-coupling and encourages API communication between microservices [3:12]. Yet, tight-coupling of the datastore as seen with a singular database in a monolith, does allow for transactions to guarantee consistency across multiple resources [4].
Being a distributed architecture following a database per-service pattern, the microservices can only provide eventual consistency and thus should emphasize transactionless coordination.
Testing
- Although one is able to gauge connascence between microservices in the controller and service layer tests, automated test integration into a CI/CD pipeline may prove beneficial in exploring how microservices leverages automated deployment machinery and devops practices to optimally achieve the microservices' testability architectural characteristic.
Application Infrastructure Patterns
Cross-cutting Concerns Pattern
The use of a microservices chassis pattern based on Spring provided aid in speeding delivery and development of the microservices-architecture.
Although it provisioned flexibility within the system via functionality such as the externalized configuration pattern. The externalized configuration pattern entails configuration property values such as database configurations and network locations be configured at runtime [3:36].
Such configuration was specified under the application.yml files in each Spring microservice, easily allowing developers to change load-balancing algorithms, database and service discovery mechanisms.
- This is known as a push-based externalization configuration pattern as the microservice's configuration is pushed from a configuration file [3:361].
When exploring a variety of patterns, rapidly changing configuration provides a great benefit to the learning experience. Although a pull-based external configuration pattern with Spring Config Server was explored, this was mitigated as it can be seen as a central point of failure with each Spring microservice being reliant on the server for configuration properties.
The Spring microservices chassis does present an issue of ossification due to its compatibility with a limited language set, being Java, Kotlin, Groovy etc. Spring Boot can support more languages via Spring Internationalization.
During the planning and development phases, many dependencies within the Spring Cloud Netflix family were discovered to be deprecated. Although still usable, implementation of such dependencies would not adhere to the objectives of using a technology stack that minimizes evolution costs. Although all deprecated dependencies were avoided within the Eureka service discovery version of the architecture, the use of Kubernetes service discovery required the deprecated Ribbon load-balancer to be used across all microservices. The exploration and implementation of using a microservices chassis provided valuable insight into the risks and benefits of using such a pattern.
An alternative to the microservices chassis pattern is the service mesh. The service mesh pattern provides key microservice features such as circuit breaker patterns and service discovery being implemented upon the networking layer, routing traffic between services in accordance to the specified features [3:380].
Service mesh provides fewer dependencies within each microservice and further allows for a large range of languages to be supported. Popular service mesh frameworks include Istio and Linkerd. Yet, the law of software architecture being that everything is a trade-off [2] still takes precedence.
Istio and Linkerd were explored during development and were found to be tightly coupled to Kubernetes. Although the resource-sharing system does use Kubernetes for deployment, the requirement of a conservative technology stack familiar to students was favoured.
As Istio and Linkerd are compatible with the Spring chassis using Kubernetes service discovery, there is future potential for the system to evolve towards service mesh use.
Security Pattern
No access token implemented, an architectural trade-off made in return for reduction in the number of points of failure the system may have.
Observability Patterns
Spring Boot actuator provided an infrastructure-independent health metric. Such an end-point provides flexibility within the system as Eureka and Kubernetes can invoke this end-point for each microservice instance, thus determining correct traffic routing [2:368].
Both GCP logs explorer and actuator have been architecturally hoisted, removing cognitive hits for developers. Exchanging this benefit for less control over system monitoring.
Service Discovery with Eureka
A key benefit of Eureka, is the ability to provide service discovery across a range of deployment platforms [2:83]. This was exercised with Eureka providing discovery for the Spring Cloud based services running on Google Kubernetes Engine as well as on a local machine.
Eureka is not aimed at running over the Kubernetes networking infrastructure but rather more suited to the deployment per VM instance pattern, thus causing network communication issues between services when deployed using Kubernetes and the service as a container pattern. Such an issue was partially solved by having the API gateway and all 3 microservices be deployed per pod.
Although one can separate the API gateway using native Kubernetes service discovery whilst deploying 3 microservices per pod with an additional external IP facing Eureka registry pod, the use of 2 separate service discovery mechanisms proves counter-productive.
Furthermore, Eureka is Spring Cloud specific, hindering language support for other types of future microservices.
Infrastructure Patterns
Service Discovery with Kubernetes
As stated within the design and implementation section, service discovery with Kubernetes as a platform-provided service discovery pattern proved to beneficially complement the microservices-architecture.
One was able to separate each microservice as an independent deployment abstraction, achieving core microservice characteristics of independent scalability and service isolation.
As stated before, Kubernetes further extends evolution of the resource-sharing system through the allowance of service mesh implementation such as Isitio.
Deployment
The use of open-source tooling provided on Google Cloud Platform being Docker, Jib and Kubernetes provided the ability for operations to be replicated using personal, School or alternative cloud provider infrastructure. Use of cloud-specific monitoring such as Google Cloud metrics instantiated simplicity, opposed to creating additional monitoring services within the architecture. Yet, such a dependence may prove problematic for development operations if the resource-sharing system is migrated elsewhere.
The external service mapping of the Cloud SQL based MySQL database proved beneficial in illustrating decentralized data management.
Additional deployment patterns that could prove beneficial exploration for the resource-sharing system include the sidecar pattern and serverless deployment.
The side-car pattern refers to running an additional supporting process or container alongside the core microservices deployment [3:410].
The serverless deployment platform entails each service running as a function specific to the cloud provider. Although such a deployment adheres to decentralized language choice for microservices, one does risk vendor lock-in, and the cost of service migration to another infrastructure may prove expensive.
Communications Patterns
Communication Style
Although synchronous and REST calls provide system inflexibility and may prove slower and asynchronous messaging, most software in production use this technique as clients interact via HTTP endpoints on the front-end side of web-based applications.
As the architecture is used within an academic environment, it will not be scaled to extreme levels. Thus, the benefit of asynchronous messaging does not outweigh the additional cost in complexity. [3:68]
Furthermore, the use of JSON is already familiar to students as a point of I/O.
The migration to a more efficient but human-unreadable binary encoding format will not provide efficacy in meeting the objectives of leveraging the system as a teaching tool.
Reliability
Although there is a lack of a circuit breaker pattern, the synchronous communication style entails that retries to a down microservice instance will only be initiated by the user, receiving a 500 status code. This is similar to a static response one may get if a Hystrix circuit breaker pattern library was implemented.
A future improvement would be the migration to an asynchronous messaging style to improve scalability and flexibility of the system, with a trade-off in increased complexity and failure points with the addition of a message broker.
External API pattern
Although the use of an API gateway pattern may prove a single point of failure if the other service's IP addresses are not exposed. One could reduce this point of failure by implementing a backends for front-ends pattern whereby there is a separate API gateway per client type (web, mobile, ect.).
Additional issues being extra latency incurred with an extra packet hop, [3:263], as well as increased thread use if the API-gateway binds to a synchronous I/O model. Fortunately, Spring Cloud gateway runs as a Netty servlet, using an asynchronous I/O model. This reduces the gateway as a performance bottleneck by only using a single event-loop thread, leveraging I/O request dispatches to an event-handler [3:268].
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