Microservices Design Patterns in .NET - Trevoir Williams - E-Book

Microservices Design Patterns in .NET E-Book

Trevoir Williams

0,0
35,18 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.
Mehr erfahren.
Beschreibung

Learn to be deliberate and intentional in your design, technology, and pattern choices when developing an application using a microservices architecture.

Key Features



  • Tackle common design problems when developing a microservices application using .NET Core
  • Explore applying S.O.L.I.D development principles in developing a stable microservice application
  • Use your knowledge to solve common microservice application design challenges

Book Description



Are you a developer who needs to fully understand the different patterns and benefits that they bring to designing microservices? If yes, then this book is for you. Microservices Design Patterns in .NET will help you appreciate the various microservice design concerns and strategies that can be used to navigate them.

Making a microservice-based app is no easy feat and there are many concerns that need to be addressed. As you progress through the chapters of this guide, you'll dive headfirst into the problems that come packed with this architectural approach, and then explore the design patterns that address these problems. You'll also learn how to be deliberate and intentional in your architectural design to overcome major considerations in building microservices.

By the end of this book, you'll be able to apply critical thinking and clean coding principles when creating a microservices application using .NET Core.

What you will learn



  • Use Domain-Driven Design principles in your microservice design
  • Leverage patterns like event sourcing, database-per-service, and asynchronous communication
  • Build resilient web services and mitigate failures and outages
  • Ensure data consistency in distributed systems
  • Leverage industry standard technology to design a robust distributed application
  • Find out how to secure a microservices-designed application
  • Use containers to handle lightweight microservice application deployment

Who this book is for



If you are a .NET developer, senior developer, software architect, or DevOps engineer who wants to explore the pros and cons, intricacies, and overall implementation of microservice architecture, then this book is for you. You'll also get plenty of useful insights if you're seeking to expand your knowledge of different design patterns and supporting technologies.



Basic experience with application and API development with .NET Core (2+) and C# will help you get the most out of this book.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB

Seitenzahl: 410

Veröffentlichungsjahr: 2023

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Microservices Design Patterns in .NET

Making sense of microservices design and architecture using .NET Core

Trevoir Williams

BIRMINGHAM—MUMBAI

Microservices Design Patterns in .NET

Copyright © 2022 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Group Product Manager: Gebin George

Publishing Product Manager: Pooja Yadav

Senior Editor: Rounak Kulkarni

Technical Editor: Maran Fernandes

Copy Editor: Safis Editing

Project Coordinator: Deeksha Thakkar

Proofreader: Safis Editing

Indexer: Manju Arasan

Production Designer: Vijay Kamble

Developer Relations Marketing Executive: Sonakshi Bubbar

Business Development Executive: Kriti Sharma

First published: January 2023

Production reference: 1161222

Published by Packt Publishing Ltd.

Livery Place

35 Livery Street

Birmingham

B3 2PB, UK.

ISBN 978-1-80461-030-5

www.packt.com

I dedicate this publication to my loving, hard-working, and irreplaceable parents, whose tremendous sacrifices gave me the best possible start in life. They remain a steady source of inspiration, and I try to make them proud of everything I do. I thank my caring, understanding wife, whose unwavering support nudged me through this tiring endeavor. I also thank my students, who constantly challenge me to remain on top of the latest technologies and concepts and always push me forward in this field.

– Trevoir Williams

Contributors

About the author

Trevoir Williams is a passionate software and system engineer with a strong drive to share the best of his knowledge with students around the globe. He holds a master’s degree in computer science, majoring in software development. He has also received multiple Microsoft Azure certifications.

His work experience includes software engineering and consulting, database development, cloud system administration, and lecturing. He enjoys teaching IT and development skills and guides students in gaining the latest knowledge with practical application in the modern industry.

About the reviewer

Marius Iulian Mihailescu is an associate lecturer at Spiru Haret University and co-founder/chief research officer at Dapyx Solution, Bucharest, Romania. His work is focused on applied/theoretical cryptography and information security, dealing with the identification process of threats and vulnerabilities using artificial intelligence and machine learning. He is currently also working as an IT project manager at the Institute for Computers, Romania, where he is responsible for the development process of several projects.

His research interests are extended to areas such as computer vision, biometrics, machine learning, artificial intelligence, and deep learning. The goal of his research is focused on understanding the real requirements of complex systems (cloud computing, big data, IoT, etc.) and their applications in different domains (such as computer forensics, behavioral psychology, and financial derivatives), and to provide real and practical solutions for guaranteeing the Confidentiality, Integrity, and Availability (CIA) of the processes.

Marius completed his Ph.D. studies in computer science on improving the security techniques for guaranteeing the confidentiality and integrity of the biometrics data at the Faculty of Mathematics and Computer Science at the University of Bucharest, Romania. During this time, he was also a part-time research associate for the ATHOS (Automated System of Authentication through Biometric Signature) project from S.C. Softwin S.R.L., where he worked on improving the load balancing mechanisms in a parallel computing system.

Table of Contents

Preface

Part 1: Understanding Microservices and Design Patterns

1

Introduction to Microservices – the Big Picture

A deep dive into microservices and its key elements

Building a monolith

Building microservices

Assessing the business need for microservices

Scalability

Availability

Development speed

Improved data storage

Monitoring

Deployment

Determining the feasibility of implementing microservices

Microservices and .NET Core

Summary

2

Working with the Aggregator Pattern

Technical requirements

Exploring DDD and its significance

Exploring the pros and cons of DDD

DDD and microservices

The purpose and use of aggregate patterns

Aggregates and aggregate roots

Relationships in aggregates

Handling relationships that span aggregates

Aggregates versus entities

Entities and why we need them

Practical uses of entities in code

A rich domain model versus an anemic domain model

Understanding and using value objects

Summary

3

Synchronous Communication between Microservices

Technical requirements

Use cases for synchronous communication

Challenges of microservice communication

Implementing synchronous communication

Implementing HTTP synchronous communication

Implementing gRPC synchronous communication

HTTP versus gRPC communication

Disadvantages of synchronous communication between microservices

Summary

4

Asynchronous Communication between Microservices

Technical requirements

Functioning with asynchronous communication

HTTP asynchronous communication

Understanding Pub-Sub communication

Understanding message queues

Understanding message bus systems

Understanding eventual consistency

Configuring a message bus (RabbitMQ or Azure Service Bus)

Implementing RabbitMQ in an ASP.NET Core web API

Implementing Azure Service Bus in an ASP.NET Core API

Disadvantages of asynchronous communication between microservices

Summary

5

Working with the CQRS Pattern

Technical requirements

Why use CQRS for microservices development?

Benefits of the CQRS pattern

Disadvantages of the CQRS pattern

Using the Mediator pattern with CQRS in .NET

Implementing a command

Creating a command model

Creating a command handler

Invoking a command

Implementing a query

Creating a query model

Creating a query handler

Invoking a query

Summary

6

Applying Event Sourcing Patterns

Technical requirements

What are events?

Key attributes of events

What can event sourcing patterns do for me?

Pros of event sourcing

Cons of event sourcing

What are domain events?

Domain events and event sourcing

Exploring domain events in our application

Creating an event store

How to store events

Implementing event sourcing using a relational database

Implementing event sourcing using a non-relational database

Reading state with CQRS

Summary

Part 2: Database and Storage Design Patterns

7

Handling Data for Each Microservice with the Database per Service Pattern

Technical requirements

How to make use of the Database-Per-Service pattern

A single database technology per service

Using different database technologies per service

Disadvantages of this pattern

Developing a database

Relational databases

Non-relational databases

Choosing a database technology

Choosing an ORM

Choosing a database development technique

Implementing the repository pattern

Summary

8

Implement Transactions across Microservices Using the Saga Pattern

Exploring the Saga pattern

Issues and considerations

Understanding and implementing choreography

Rolling back on failure

Pros and cons

Understanding and implementing orchestration

Rolling back on failure

Pros and cons

Summary

Part 3: Resiliency, Security, and Infrastructure Patterns

9

Building Resilient Microservices

Technical requirements

The importance of service resiliency

Possible failure scenarios and how to handle them

Implementing resiliency with caching and message brokers

Using a message broker

Using a caching layer

Using Redis Cache

Implementing retry and circuit breaker policies

Retry policy with Polly

Circuit breaker policy with Polly

Summary

10

Performing Health Checks on Your Services

Technical requirements

Health checks and microservices

The liveness health check

Readiness health checks

Implementing ASP.NET Core health checks

Adding liveness health checks

Adding readiness health checks

Configuring health probes in orchestrators

Summary

11

Implementing the API and BFF Gateway Patterns

Technical requirements

What is the API gateway pattern?

Advantages of an API gateway

Disadvantages of an API gateway

Implementing the API gateway pattern

Thick API gateways

Implementing an API gateway using Azure API Management

Implementing an API gateway using Ocelot

Adding cache management

Adding rate limiting

Adding response aggregation

Backend for Frontend pattern

BFF pattern using Ocelot

Summary

12

Securing Microservices with Bearer Tokens

Technical requirements

Bearer tokens for securing communications

Understanding bearer tokens

Implementing bearer token security

Securing API with bearer tokens

Generating and issuing bearer tokens

Using IdentityServer4 to secure microservices

Configuring IdentityServer

Securing an API using IdentityServer

Securing the Ocelot API gateway with IdentityServer

Additional API security considerations

Summary

13

Microservice Container Hosting

Technical requirements

Using containers in microservices development

What can containers do for me?

Understanding Docker

Understanding container images

Pros and cons of containers

Authoring a Dockerfile

Launching a containerized application

Using native .NET container support

Understanding docker-compose and images

Adding docker-compose to a project

Publishing containers to a container registry

Public versus private container registries

Creating and uploading custom images

Summary

14

Implementing Centralized Logging for Microservices

Technical requirements

Logging and its importance

Choosing what to log

Using the .NET logging API

Adding Serilog

Log aggregation and its uses

Integrating with Seq

Distributed log tracing

Enhanced logging for distributed tracing

Summary

15

Wrapping It All Up

Aggregator pattern

Synchronous and asynchronous communication

CQRS

Event sourcing patterns

Database per service pattern

Relational databases

Non-relational databases

One database for all services

One database per service

Using the saga pattern across services

Resilient microservices

Importance of health checks

API Gateways and backend for frontend

Bearer token security

Containers and microservices

Centralized logging

Summary

Index

Other Books You May Enjoy

Preface

Hello there! We are here to explore design and development patterns that we can leverage when building a microservice application with .NET. Microservice architecture involves separating a potentially complex application into smaller, more maintainable services that must work together. Essentially, we take one big application and break it down into smaller parts.

This approach introduces a fresh crop of complexities in the application’s design since these now separate services need to collaborate to deliver a unified experience to the end user. As such, we need to understand the various drawbacks of this architectural approach and strategize on how we can address the various concerns.

We will focus on using .NET, given that Microsoft has a proven track record of releasing and supporting top-notch tooling and support for the most recent technologies that allow us to develop cutting-edge solutions. Some of the reasons we focus on development with .NET are as follows:

Well maintained: Microsoft is constantly pushing the boundaries and introducing new ways to accomplish old things and new ways to implement solutions and increase productivity. It is a well-maintained, supported, and documented ecosystem.Performance: .NET increases its performance with each new release. Microservices must be as performant and responsive as possible to ensure that the end user’s experience is as clean as possible.Cross-platform: .NET Core is cross-platform and can be deployed on virtually any technology stack. This reduces the limitations surrounding deploying and supporting services written using .NET technology.Support for various technologies: Each problem has a technology that helps us to implement a solution. .NET has support for many technologies, making it a great candidate for universal development needs.

We want to ensure that we understand the possibilities and strategies needed while developing an application based on microservices architecture. We will review the theory behind each problem and then explore the potential solutions and technologies that help us to implement the best possible solution to our challenges while developing a solution.

Who this book is for

This book is designed for .NET developers who wish to demystify the various moving parts of microservices architecture. To get the most out of the book, you should ideally fit into one of these categories:

Team leads: Leaders who need to understand the various moving parts and the theory behind design decisions that need to be madeSenior developers: Developers who need to appreciate how to guide the development efforts and implement complex blocks of codeIntermediate .NET developers: .NET developers who have a working knowledge of the .NET ecosystem and want to dig deeper into developing more complex solutions

The overall content of this book will assist you in understanding the dynamics of microservices application design and assist you in reaching the next level of development.

What this book covers

Chapter 1, Introduction to Microservices – the Big Picture, looks at microservice architecture at a high level and seeks to understand some of the early problems we might encounter and explores design patterns that address them.

Chapter 2, Working with the Aggregator Pattern, explores how domain-driven design and the aggregate pattern lay the foundation for scoping requirements and the foundation of microservice design.

Chapter 3, Synchronous Communication between Microservices, explores how we make microservices communicate synchronously and the potential drawbacks of this method.

Chapter 4, Asynchronous Communication between Microservices, looks at asynchronous communication between services, which allows us to send data and move along, regardless of the availability or potential long runtime of the other microservices being called.

Chapter 5, Working with the CQRS Pattern, explores the CQRS pattern and why it is useful in microservices development.

Chapter 6, Applying Event Sourcing Patterns, discusses the intricacies of event sourcing and how we can implement this to ensure that our data between services stays in sync.

Chapter 7, Handling Data for Each Microservice with Database per Service Pattern, covers the best practices surrounding implementing different databases in different services.

Chapter 8, Implement Transactions across Microservices Using the Saga Pattern, explores the Saga pattern and how it helps us implement transactions across our microservices.

Chapter 9, Building Resilient Microservices, reviews implementing retry and exit strategy logic for more resilient communication between services.

Chapter 10, Performing Health Checks on Your Services, reviews how we can implement health checks in our ASP.NET Core APIs and why they are essential.

Chapter 11, Implementing API and BFF Gateway Patterns, dives into implementing API gateways, the backed for frontend pattern, and how they help us to create a robust microservices application.

Chapter 12, Securing Microservices with Bearer Tokens, reviews how bearer tokens can secure communications with each service.

Chapter 13, Microservice Container Hosting, explores containerization and how we can leverage containers to efficiently host our microservices.

Chapter 14, Implementing Centralized Logging for Microservices, explores the steps and best practices for aggregating logs from several services into one viewing area.

Chapter 15, Wrapping It All Up, discusses the key points from each chapter and highlights how each plays a role in developing a microservices application.

To get the most out of this book

You will need knowledge of API development using ASP.NET Core and basic database development knowledge. This book is best suited for intermediate-level developers looking to improve their understanding of development design patterns.

Software/hardware covered in the book

Operating system requirements

ASP.NET Core 6/7

Windows, macOS, or Linux

C# 9/10

Windows, macOS, or Linux

Docker

Windows, macOS, or Linux

Most examples are shown using NuGet package manager commands and Visual Studio 2022. These commands can be translated into dotnet CLI commands, which can be used on any operating system and with alternative IDEs to Visual Studio.

Download the example code files

You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Microservices-Design-Patterns-in-.NET. If there’s an update to the code, it will be updated in the GitHub repository.

We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!

Download the color images

We also provide a PDF file that has color images of the screenshots and diagrams used in this book. You can download it here: https://packt.link/dD3Jv.

Conventions used

There are several text conventions used throughout this book.

Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, commands, and keywords. Here is an example: “As it stands, our CreateAppointmentHandler will handle everything that is needed in the situation.”

A block of code is set as follows:

public class AppointmentCreated : IDomainEvent     {         public Appointment { get; set; }         public DateTime ActionDate { get; private set; }

Bold: Indicates a new term, an important word, or words that you see onscreen. For instance, words in menus or dialog boxes appear in bold. Here is an example: “Select System info from the Administration panel.”

Tips or important notes

Appear like this.

Get in touch

Feedback from our readers is always welcome.

General feedback: If you have questions about any aspect of this book, email us at [email protected] and mention the book title in the subject of your message.

Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.

Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.

If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.

Share Your Thoughts

Once you’ve read Microservices Design Patterns in .NET, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.

Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.

Download a free PDF copy of this book

Thanks for purchasing this book!

Do you like to read on the go but are unable to carry your print books everywhere?

Is your eBook purchase not compatible with the device of your choice?

Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.

Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.

The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily

Follow these simple steps to get the benefits:

Scan the QR code or visit the link below

https://packt.link/free-ebook/9781804610305

Submit your proof of purchaseThat’s it! We’ll send your free PDF and other benefits to your email directly

Part 1: Understanding Microservices and Design Patterns

This part focuses on the fundamentals of microservices design patterns. You will learn about the many moving parts and scoping techniques needed for deciding the overall approach to design a microservices application. By the end of this part, you will realize the intricacies surrounding this development pattern and understand some of the pros and cons of this approach.

This part has the following chapters:

Chapter 1, Introduction to Microservices – The Big PictureChapter 2, Working with the Aggregator PatternChapter 3, Synchronous Communication between MicroservicesChapter 4, Asynchronous Communication between MicroservicesChapter 5, Working with the CQRS PatternChapter 6, Applying Event Sourcing Patterns

1

Introduction to Microservices – the Big Picture

Microservices are being featured in every avenue of software development. Microservices are a software development style that has been touted to increase development speed and efficiency while improving software scalability and delivery. This development technique is not unique to any stack and has become extremely popular in Java, .NET, and JavaScript (Node JS) development. While the use of microservices is seen as a pattern, there are several subpatterns that are employed to ensure that the code base is as effective as possible.

This chapter is the first of this 15-chapter book, which will cover design patterns used in microservices. We will be focusing on implementing them using the .NET Core development stack, and you will learn how code can be written and deployed. You will learn about design and coding patterns, third-party tools and environments, and best practices for handling certain scenarios in application development with microservices.

In this chapter, we are going to cover the following topics:

A deep dive into microservices and its key elementsAssessing the business need for microservicesDetermining the feasibility of implementing microservices

A deep dive into microservices and its key elements

Traditionally, in software development, applications have developed as a single unit or monolith. All of the components are tightly coupled, and a change to one component threatens to have rippling effects throughout the code base and functionality. This makes long-term maintenance a major concern and can hinder developers from rolling out updates quickly.

Microservices will have you assess that monolith, break it into smaller and more perceivable applications. Each application will relate to a subsection of the larger project, which is a called domain. We will then develop and maintain the code base per application as independent units. Typically, microservices are developed as APIs and may or may not interact with each other to complete operations being carried by users through a unifying user interface. Typically, the microservice architecture comprises a suite of small independent services, which communicate via HTTP (REST APIs) or gRPC (Google Remote Procedure Call). The general notion is that each microservice is autonomous, has a limited scope, and aids in a collectively loosely coupled application.

Building a monolith

Let’s imagine that we need to build a health facility management web application. We need to manage customer information, book appointments, generate invoices, and deliver test results to customers. If we were to itemize all the steps needed to build such an application, key development and scoping activities would include the following:

Model the application, and scope the requirements for our customer onboarding, user profiles, and basic documents.Scope the requirements surrounding the process of booking an appointment with a particular doctor. Doctors have schedules and specialties, so we have to present the booking slots accordingly.Create a process flow for when a match is found between a customer and a doctor. Once a match is found, we need to do the following:Book the doctor’s calendar slotGenerate an invoicePotentially collect a payment for the visitSend email notifications to the customer, doctor, and other relevant personnelModel a database (probably relational) to store all this information.Create user interfaces for each screen that both customers and the medical staff will use.

All of this is developed as one application, with one frontend talking to one backend, one database, and one deployment environment. Additionally, we might throw in a few third-party API integrations for payment and email services. This can be load balanced and hosted across multiple servers to mitigate against downtime and increase responsiveness:

Figure 1.1 – Application building

However, this monolithic architecture introduces a few challenges:

Attempts to extend functionality might have ripple effects through multiple modules and introduce new database and security needs.

Potential solution: Perform thorough unit and integration testing.

The development team runs the risk of becoming very dependent on a particular stack, making it more difficult to keep the code base modern.

Potential solution: Implement proper versioning strategies and increment them as the technology changes.

As the code base expands, it becomes more difficult to account for all of the moving parts.

Potential solution: Use clean architectural methods to keep the code base loosely coupled and modular.

The reality is that we can overcome some of these challenges with certain architectural decisions. This all-in-one architecture has been the de facto standard, and frankly, it works. This project architecture is simple, easy enough to scope and develop, and is supported by most, if not all, development stacks and databases. We have been building them for so long that perhaps we have become oblivious to the real challenges that prevail as we try to extend and maintain them in the long term.

Figure 1.2 shows the typical architecture of a monolithic application:

Figure 1.2 – One user interface is served by an API or code library with business logic and is serviced by one database

Now that we have explored the monolithic approach and its potential flaws, let us review a similar application built using microservices.

Building microservices

Now, let us take the same application and conceptualize how it could be architected using microservices. During the design phase, we seek to identify the specific functionalities for each tranche of the application. This is where we identify our domains and subdomains; then, we begin to scope standalone services for each. For example, one domain could be customer management. This service will solely handle the user account and demographic information. Additionally, we could scope bookings and appointments, document management, and finally, payments. This then brings another issue to the foreground: we have dependencies between these three subdomains when we need service independence instead. Using domain-driven design, we then scope out where there are dependencies and identify where we might need to duplicate certain entities. For instance, a customer needs representation in the booking and appointments database as well as payments. This duplication is required if we are using separate databases per service (which is strongly encouraged).

The microservices require us to properly scope the flow of operations that involve multiple services playing a part. For instance, when making a booking, we need to do the following:

Retrieve the customer making the booking.Ensure that the preferred time slot is available.If available, generate an invoice.Collect the payment.Confirm the appointment.

That process alone has some back-and-forth processing between the services. Properly orchestrating these service conversations is very critical to having a seamless system and adequately replacing a monolithic approach. Therefore, we introduce various design patterns and approaches to implementing our code and infrastructure. Even though we break potentially complex operations and workflows into smaller and more perceivable chunks, we end up in the same position where the application needs to carry out a specific operation and carry out the original requirements as a whole.

Figure 1.3 shows the typical architecture of a microservices application:

Figure 1.3 – Each microservice is standalone and unifies in a single user interface for user interactions

Now that you are familiar with the differences between the monolithic and microservices approaches, we can explore the pros and cons of using the microservices design pattern.

Assessing the business need for microservices

As we have seen so far, microservices are not easy to author, and they come with many cross-cutting concerns and challenges. It is always important to ask yourself Why? and Do I really need it? before implementing any design patterns.

At a high level, some benefits of this approach are listed as follows:

ScalabilityAvailabilityDevelopment speedImproved data storageMonitoringDeployment

In the following sections, we will dive into the details of each.

Scalability

In the monolithic approach, you scale all or nothing. In microservices, it is easier to scale individual parts of the application and address specific performance gaps as they arise. If a vaccine becomes widely available and customers are encouraged to book an appointment online, then we are sure to experience a large load during the first few weeks. Our customer microservice might not be too affected by that, but we will need to scale our booking and appointments and payments services.

We can scale horizontally, which means that we can allocate more CPU and RAM when the load increases. Alternatively, we can scale vertically by spawning more instances of the service to be load balanced. The better method is relative to the service’s needs. Using the right hosting platforms and infrastructure allows us to automate this process.

Availability

Availability means the probability of a system being operational at a given time. This metric goes hand in hand with the ability to scale, but it also addresses the reliability of the underlying code base and hosting platform. The code base plays a big part in that, so we want to avoid, as much as possible, a single point of failure. A single point of failure affects the entire system if it fails at any point. For example, we will be exploring the gateway pattern, where we will aggregate all services behind one point of entry. For our distributed services to remain available, this gateway must be always online.

This can be achieved by having vertical instances that balance the load and distribute the expected responsiveness of the gateway and, by extension, the underlying services.

Development speed

Given that the application has been broken into domains, developers can focus their efforts on ensuring that their set of features is being developed efficiently. This also contributes to how quickly features can be added, tested, and deployed. It will now become a practical approach to have one team per subdomain. Additionally, it becomes much easier to scope the requirements for a domain and focus on fewer functional requirements for a piece of work. Each team can now be independent and own the service from development to deployment.

This allows Agile and DevOps methodologies to be easier to implement, and it is easier to scope resource requirements per team. Of course, we have seen that services will still need to communicate, so we will still have to orchestrate the integration between the teams. So, while each team is independent, they will still need to make their code and documentation available and easy enough to access. Version control also becomes important since the services will be updated over time, but this must be a managed process.

Improved data storage

Our monolithic application uses one database for the entire application. There are situations where you might end up using one database for multiple microservices, but this is generally discouraged, and a database-per-service approach is preferred. Services must be autonomous and independently developed, deployed, and scaled. This is best accomplished if each service has its own data storage. It makes even more sense when you consider that the type of data being stored might influence the type of data storage that is used. Each service might require a different type of data store, ranging from relational database storage such as Microsoft SQL Server to document-based database storage such as Azure Cosmos DB. We want to ensure that changes to a data store will only affect the associated microservice.

Of course, this will bring its own challenges where data will need to be synchronized across the services. In the monolith, we could wrap all steps inside one transaction, which might lead to performance issues for potentially long-running processes. With microservices, we face the challenge of orchestrating distributed transactions, which also introduces performance risks and threatens the immediate consistency of our data. At this point, we must turn to the concept of eventual consistency. This means that a service publishes an event when its data changes and subscribing services use that event as a signal to update their own data. This approach is made possible through event-sourcing patterns. We accept the risk that, for a period, data might be inconsistent across subdomains. Message queue systems such as Kafka, RabbitMQ, and Azure Service Bus are generally used to accomplish this.

Monitoring

One of the most important aspects of a distributed system is monitoring. This allows us to proactively ensure uptime and mitigate against failures. We need to be able to view the health of our service instances. We also begin to think about how we can centralize logs and performance metrics in a unified manner, sparing us the task of going to each environment manually. Tools such as Kibana, Grafana, or Splunk allow us to create a rich dashboard and visualize all sorts of information about our services.

One very important bit of information is a health check. Sometimes, a microservice instance can be running but is failing to handle requests. For example, it might have run out of database connections. With health checks, we can see a quick snapshot of the service’s health and have that data point returned to the dashboard.

Logging is also a crucial tool for monitoring and troubleshooting. Typically, each microservice would write its own logs to files in its environment. From these logs, we can see information about errors, warnings, information, and debug messages. However, this is not efficient for a distributed system. At this point, we use a log aggregator. This gives us a central area to search and analyze the logs from the dashboards. There are a few log aggregators you can choose from such as LogStash, Splunk, or PaperTrail.

Deployment

Each microservice needs to be independently deployable and scalable. This includes all the security, data storage, and additional assets that our services use. They must all live on physical or virtual servers, whether on-premises or in the cloud. Ideally, each physical server will have its own memory, network, processing, and storage. A virtual infrastructure might have the same physical server with the appropriate resource allocations per service. Here, the idea is that each microservice instance is isolated from the other and will not compete for resources.

Now, each microservice will need its own set of packages and supporting libraries. This then becomes another challenge when provisioning different machines (physical or virtual) and their operating systems. We then seek to simplify this by packaging each microservice as a container image and deploying it as a container. The container will then encapsulate the details of the technology used to build a service and provide all the CPU, memory, and microservice dependencies needed for operation. This makes the microservice easy to move between testing and production environments and provides environment consistency.

Docker is the go-to container management system and works hand in hand with container orchestration services. Orchestration becomes necessary to run multiple containers across multiple machines. We need to start the correct containers at the correct time, handle storage considerations, and address potential container failures. All of these tasks are not practical to handle manually, so we enlist the services of Kubernetes, Docker Swarm, and Marathon to automate these tasks. It is best to have all the deployment steps automated and be as cost-effective as possible.

Then, we look to implement an integrated pipeline that can handle the continuous delivery of our services, with as minimal effort as possible, while maintaining the highest level of consistency possible.

We have explored quite a bit in this section. We reviewed why we might consider using a microservices approach in our development efforts. Also, we investigated some of the most used technologies for this approach. Now, let us turn our attention to justifying our use of microservices.

Determining the feasibility of implementing microservices

As we explore the microservices approach, we see where it does address certain things, while introducing a few more concerns. The microservices approach is certainly not a savior for your architectural challenges, and it introduces quite a few complexities. These concerns and complexities are generally addressed using design patterns, and using these patterns can save time and energy.

Throughout this book, we will explore the most common problems we face and look at the design pattern concepts that help us to address these concerns. These patterns can be categorized as follows.

Let us explore what each pattern entails:

Integration patterns: We have already discussed that microservices will need to communicate. Integration patterns serve to bring consistency to how we accomplish this. Integration patterns govern the technology and techniques that we use to accomplish cross-service communications.Database and storage design patterns: We know that we are in for a challenge when it comes to managing data across our distributed services. Giving each service its own database seems easy until we need to ensure that data is kept consistent across the different data stores. There are certain patterns that are pivotal to us maintaining a level of confidence in what we see after each operation.Resiliency, security, and infrastructure patterns: These patterns seek to bring calm and comfort to a brewing storm. With all the moving parts that we have identified, it is important to ensure that as many things as possible are automated and consistent in the deployment. Additionally, we want to ensure that security is adequately balanced between the system needs and a good user experience. These patterns help us to ensure that our systems are always performing at peak efficiency.

Next, let us discuss using .NET Core as our development stack for microservices.

Microservices and .NET Core

This book addresses implementing microservices and design patterns using .NET Core. We have mentioned that this architectural style is platform agnostic and has been implemented using several frameworks. Comparably, however, ASP.NET Core makes microservices development very easy and offers many benefits, including cloud integrations, rapid development, and cross-platform support:

Excellent tooling: The SDK required for .NET development can be installed on any operating system. This is further complemented by their lightweight and open source development tool, Visual Studio Code. You can easily create an API project by running dotnet new webapi on your computer. If you prefer the fully powered Visual Studio IDE, then you might be limited to Windows and macOS. You will have all the tools you need to be successful regardless of the operating system.Stability: At the time of writing this book, the latest stable version is .NET 7, with standard term support. The .NET development team is always pushing the envelope and ensuring that reverse compatibility is maintained with each major version release. This makes updating to the next version much less difficult, and you need not worry about too many breaking changes all at once.Containerization and scaling: ASP.NET Core applications can easily be mounted on a Docker container, and while this is not necessarily new, we can all appreciate a guaranteed render speed and quality. We can also leverage Kubernetes and easily scale our microservices using all the features of K8s.

.NET development has come a long way, and these are exciting times to push the boundaries of what we can build, using their tools and services.

Summary

By now, I hope you have a better idea of what microservices are, why you may or may not end up using this architectural style, and the importance of using design patterns. In each of the chapters of this book, we will explore how to use design patterns to develop a solid and reliable system based on microservices, using .NET Core and various supporting technologies.

We will remain realistic and explore the pros and cons of each of our design decisions and explore how various technologies play integral parts in helping us to tie it all together.

In this chapter, we explored the differences between designing a monolith and microservices, assessed the feasibility of building microservices, and explored why .NET Core is an excellent choice for building microservices

In the next chapter, we will look at implementing the Aggregator Pattern in our microservices application.

2

Working with the Aggregator Pattern

In the previous chapter, we looked at some of the key elements that make up microservices. We will turn our attention to more practical applications of these concepts, starting with the aggregator pattern and domain-driven design. These combine to set a premise for scoping an application being built on microservices design principles.

The aggregator pattern and domain-driven design go hand in hand. For now, we will refer to domain-driven design as DDD. So, a DDD aggregate is a group of domain objects, combined as a single unit. In practicality, we might have a customer record different from its documents, but it is prudent of us to display all of these bits of data as a single point of data, an aggregate.

After reading this chapter, we will be able to do the following:

Understand DDDUnderstand how to derive domains in a system processUnderstand the importance of aggregates and the aggregate patternDistinguish between aggregates and entitiesUnderstand value objects and their role in the design process

Technical requirements

The code references used in this chapter can be found in the project repository, which is hosted on GitHub at https://github.com/PacktPublishing/Microservices-Design-Patterns-in-.NET/tree/master/Ch02

Ensure that you have at least one of the following software installed on your machine to be able to execute this code (use the links to download and install):

Visual Studio 2022: https://visualstudio.microsoft.com/vs/Visual Studio Code: https://code.visualstudio.com/download

Exploring DDD and its significance

DDD is a software design approach that encourages us as developers to assess processes and subprocesses and decipher all the atomic elements therein. Atomic means that one process might have many moving parts, and while they all combine to give one output, they have their own routines to carry out. Each subprocess can be seen as self-governing and can further be attributed to a domain. This motivates us to break up a monolith into independent microservices that do their own thing against their own data. That is a domain.

Before we go much further, let’s take some time to explore certain keywords and their definitions:

Models: These are abstractions that define aspects of a domain and are used to solve domain problems. We organize information about the target domain into smaller pieces and call them models. A model is a central point of reference in our design and development process. These models can then be grouped into logical modules and dealt with one at a time. A domain contains too much information for just one model and sometimes, parts of the information can just be omitted. For instance, our healthcare management system does need to capture customer information, but we do not need to know their eye and hair colors. This might be a simple example, but it might get far more complicated than that in a real scenario. Sifting through the relevant parts of the body of knowledge will require close collaboration with developers, domain experts, and fellow designers.Ubiquitous language: This is a language that is unique to a domain model and is used by team members within the context of activities related to the specific domain. We have already established that models for a domain need to be developed through collaboration with domain experts. Given the difference in skillsets and perceptions, there will be communication barriers. Developers tend to think and speak about concepts relative to programming. They generally think and talk in terms of inheritance, polymorphism, and so on. Unfortunately, domain experts don’t usually know or care for any of that. Domain experts will use their own jargon and terms that developers will not understand. This gap in communication does not bode well for any team.Bounded context: This defines boundaries in a system or subsystem that informs the work that a particular team will carry out and the focal point of their efforts, within their domain. A bounded context is a logical boundary of a domain, where terms and rules apply. All terms, definitions, and concepts inside this boundary form the ubiquitous language. Establishing bounded contexts is a core step in DDD, and it is strategically used in scoping large models, in large teams. In DDD, we subdivide our larger models into bounded contexts and then we scope the relationships that exist in between. Context mapping in DDD can be confusing without real-world examples. Let’s use our healthcare management system as a sample implementation for two scenarios that use bounded context maps and learn to analyze the relationships between the maps. Say we have the context of document management and patient appointments. Both have unrelated and related concepts. Documents only exist in a document management system but will have a reference to patients. Context mapping is a common strategy used in DDD to depict the relationships that exist between bounded contexts.

Figure 2.1 shows a relationship between two domains:

Figure 2.1 – Each domain is standalone, but sometimes data overlaps. Both appointment and document management need patient data

Let’s take a step back from microservice design patterns and assess what it takes to just build software. A domain is a category of business rules and operations. If your software is to be used in a bank, then the domain is banking; if it is used in a hospital, then the domain is healthcare.

So, the software we develop must complement the domain in order to solve the overall problem. The core concepts and elements of the domain must be present in the software’s design and models.

Exploring the pros and cons of DDD

DDD is a big commitment. It promotes focus on smaller, individual pieces of a domain, and the resulting software is more flexible. It breaks our application into smaller chunks and allows us to more easily modify application parts and components, with fewer side effects. The application’s code tends to be well organized and highly testable, and the business logic for the domain is isolated to that particular code base. Even if you don’t use DDD end to end for a project, the principles can be beneficial to your application implementation activities. DDD is best used for breaking down complex business logic. It is not suitable for applications with simple requirements and business logic for creating, adding, updating, and deleting data. DDD is time-consuming and requires expert-level domain knowledge. So, bear in mind that non-technical resource persons will be required and have to be available throughout the duration of a project.

Now that we understand DDD at a high level, let’s explore how the concepts that it promotes tie into microservice design patterns.

DDD and microservices

Implementations of applications that have been scoped using DDD are best implemented through microservices. By now, we can appreciate that a microservice architecture promotes dividing one large concept for an application into self-contained and independent services. So, to use the concept of boundaries and context, each microservice serves its own bounded context. Each one will have its own models, language, business rules, and technology stack.

This is not a catch-all fix though, since perfect alignment between a microservice and a bounded context might not always be true, but some applications, including ours, are perfect candidates for microservices and DDD.

We can consider a number of scenarios where we can isolate certain services that are not the most obvious ones and wouldn’t have been originally scoped as bounded contexts. Take, for example, email and alert systems. It is easy enough to place that logic and functionality in the web application, such that when an appointment is submitted, we send confirmations to our patients and alerts to the staff. This is reasonable, but we could also create separate message queue-based services that serve the sole purpose of delivering these messages. That way, the web application has even less responsibility, and we run less risk of inadvertently modifying UI logic when addressing an email or alert maintenance concern.

Ultimately, because DDD suggests that we separate contexts into standalone tranches, a microservice architecture is the perfect development pattern to support this ambition. Bear in mind that DDD serves as an initial guideline to carve out business rules that can stand on their own, and each microservice will be developed to support that set of business rules, as well as implement supporting services in the most efficient and loosely coupled manner.

Now that we can appreciate how DDD and microservices go hand in hand, let’s begin looking into the aggregator pattern and how we can begin assessing the different models and data that need to be captured.

The purpose and use of aggregate patterns

The aggregate pattern is a specific software design pattern within DDD. It promotes the collection of related entities and aggregates them into a unit.

Aggregates make it easier to define ownership of elements in large systems. Without them, we run the risk of sprawling and trying to do too much. After we have identified the different contexts in the domain, we can then begin to extract the exact data we need from potentially multiple contexts and sources and model them.

Aggregates and aggregate roots

An aggregate comprises one or more entities and value object models that, in one way or another, interact. This interaction then encourages us to treat them as a unit for data changes. We also want to ensure that, at all times, there is consistency in the aggregate before making changes. In our concept of a healthcare management system, we have already scoped that we have a patient, who more than likely also has an address. A set of changes to a patient record and their address should be treated as a single transaction. We also want to consider that aggregates have roots or a parent object for all other aggregate members.

Aggregates make it easier to enforce certain rules for data and validation across multiple objects. So, in our example so far, a patient can have multiple addresses but needs to have at least one to be in a valid state. These kinds of constraints are easier to apply across the board from a higher level of the root. It is also easier to ensure that data changes follow ACID (Atomicity, Consistency, Isolation, and Durability) principles. We will explore these more in a later chapter.