+ All Categories
Home > Technology > Architecting Microservices in .Net

Architecting Microservices in .Net

Date post: 23-Jan-2018
Category:
Upload: richard-banks
View: 8,311 times
Download: 1 times
Share this document with a friend
71
Architecting Buzz Word Compliant Microservices in .NET Richard Banks @rbanks54 richard-banks.org
Transcript
Page 1: Architecting Microservices in .Net

Architecting Buzz Word Compliant Microservices in .NET

Richard Banks

@rbanks54

richard-banks.org

Page 2: Architecting Microservices in .Net

Overview

1. Why?

2. Architecture

3. Implementation

4. Deployment

Page 3: Architecting Microservices in .Net

Preamble

As an industry we’re still learning and maturing

Tooling for microservices is akin to frameworks for JavaScript

Many microservice implementations aren’t ‘pure’… and that’s OK

Docker on Windows is still a work in progress

Out of scope: Azure Service Fabric, Akka.NET, etc.

Lots of bullet points so you can use this deck as a reference

Page 4: Architecting Microservices in .Net

The Microservices Pitch

Page 5: Architecting Microservices in .Net

SHINY! SHINY! SHINY! SHINY! SHINY!

Shiny! Shiny! Shiny! Shiny! Shiny!

Shiny! Shiny! Shiny! Netflix!! Shiny!

Shiny! Shiny! Amazon!! Shiny! Shiny! Shiny!

Shiny! Unicorns!! Shiny! Shiny! Shiny! Shiny!

Page 6: Architecting Microservices in .Net

OK. More seriously…

Greater flexibility & scalability

More readily and easily evolvable systems

Independently deployable parts

Improved technical agility

Independent development teams

Page 7: Architecting Microservices in .Net

A few more reasons? Sure!

Resilience. A failure in one service shouldn’t wipe out the whole system.

Tech stack flexibility. Right tool for the right job.

Smaller services are easier to understand and maintain.

A potential migration approach for legacy systems

Page 8: Architecting Microservices in .Net

The Reality

Page 9: Architecting Microservices in .Net

WTF! OMG! Gah! <update resume />

Isn’t this meant to be easy?!

I can’t tell how it fits together anymore!

It’s more brittle now than it ever was!

Performance is terrible!!

I still need to make code changes in multiple services at once! Yyyy?!

Page 10: Architecting Microservices in .Net

Why?

Distributed systems are hard

Eventual consistency messes with your head

Old habits result in a distributed “big ball of mud”

Challenges debugging across multiple services

People tend to forget good design practices through underuse (legacy habits)

Page 11: Architecting Microservices in .Net

Other things to deal with

Shared databases across services?

Do transactions across services mean we need 2 phase commits?

What is the current “version” of the system?

How do we do authentication and authorization at a service level?

What do testers actually, you know… test?

Page 12: Architecting Microservices in .Net

Architecture

Page 13: Architecting Microservices in .Net

A quick reminder

Architecture isn’t about technology alone.

Think of your teams and their skills.

Consider your organisation’s structure.

Keep your architecture simple so you can meet your customer’s needs in the most cost effective way.

Don’t build what you don’t need.

Don’t build what you might need!

Page 14: Architecting Microservices in .Net

Key goals Independent, loosely coupled services

Cheap to replace, easy to scale

Fault tolerant, version tolerant services

Page 15: Architecting Microservices in .Net

Ports and Adapters(also called Hexagonal or Onion or Clean architecture)

http://blog.mattwynne.net/2012/05/31/hexagonal-rails-objects-values-and-hexagons/http://www.slideshare.net/fabricioepa/hexagonal-architecture-for-java-applications/10

Page 16: Architecting Microservices in .Net

An over simplified view

http://www.kennybastani.com/2015/08/polyglot-persistence-spring-cloud-docker.html

Page 17: Architecting Microservices in .Net

How should services communicate?

http://www.slideshare.net/adriancockcroft/monitorama-please-no-more/31

Page 18: Architecting Microservices in .Net

Keep CommsSimple

Use language & platform agnostic communications

One synchronous approach(e.g. JSON over HTTP)

One asynchronous approach(e.g. AMQP using RabbitMq)

Consistency in comms reduces complexity.

Page 19: Architecting Microservices in .Net

Synchronous Comms infersTemporal Coupling

If your services use synchronous comms, you need to handle failures and timeouts.

Use a circuit breaker pattern

Design with failures in mind (and test it!)

Netflix created the “chaos monkey” for testing failures in production.

http://www.lybecker.com/blog/2013/08/07/automatic-retry-and-circuit-breaker-made-easy/

Page 20: Architecting Microservices in .Net

Identify your business transactions

One user request may result in tens, or hundreds, of microservice calls

Treat each user request as a logical business transaction

Add a correlation ID to every user/UI request

Aids with request tracing and performance optimisation

Aids with debugging, failure diagnosis and recovery

Page 21: Architecting Microservices in .Net

Evolvable APIs via Consumer Driven Contracts

A concept from the SOA days:

“Services aren't really loosely coupled if all parties to a piece of service functionality must change at the same time.”

In SOA days WSDLs and XSDs were meant to solve this. Yeah… right.

With HTTP APIs, have a look at Pact

http://www.infoq.com/articles/consumer-driven-contractshttps://github.com/SEEK-Jobs/pact-nethttps://www.youtube.com/watch?v=SMadH_ALLII

Page 22: Architecting Microservices in .Net

Loose Coupling and Service Discovery

Loose coupling implies no hard coded URLs.

Service discovery isn’t new (remember UDDI?)

Microservices need a discovery mechanism. E.g. Consul.io & Microphone

https://github.com/rogeralsing/Microphone

Page 23: Architecting Microservices in .Net

Loose Coupling implies Data Duplication

For services to be independent:

they cannot rely on another service being up at the same time (temporal coupling), and

they should cache any external data they need

Page 24: Architecting Microservices in .Net

Implementation Patterns

Page 25: Architecting Microservices in .Net

Design Patterns & Components

Domain Driven Design – Align services to Domain Contexts, Aggregates & Services

CQRS – Command Query Responsibility Separation. Scale reads and writes independently.

SQL/NoSQL – Persistent, easily rebuilt caches for query services.

Versioning – APIs are your contracts, not versions of binaries.

Page 26: Architecting Microservices in .Net

Design Patterns & Components

Message Bus – Reliable, async comms.

Optimistic Concurrency – No locking!

Event Sourcing – Persist events, not state. Avoid 2-PC hassles.

Application Services – encapsulate access to microservices; optimise for client needs.

Page 27: Architecting Microservices in .Net

Event Sourcing. Really?

Yes, really :-)

When a domain object is updated, we need to communicate that change to all the other interested microservices.

We could use 2-phase commit, and we could also drink battery acid.

With ES we simply save a domain event to our event store and then publish it on the bus.

Interested other services subscribe to events and take action as they see fit.

Page 28: Architecting Microservices in .Net

WARNINGS

The “100 line” rule is just silly.

These nano-services are effectively a service-per-method.

Turn your app into thousands of RPC calls! Yay!

Services should be “business services” and provide business value. Again, apply DDD concepts to determine service boundaries.

Page 29: Architecting Microservices in .Net

Rule of thumb for sizing microservices

Have a single purpose E.g. manage state of domain entities

E.g. send emails

E.g. authenticate users

Be unaware of other services (in the core)

Consider use case boundaries/bounded contexts

Page 30: Architecting Microservices in .Net

Architecture PicturesIt’s not architecture if there’s no boxes and lines!

Page 31: Architecting Microservices in .Net

Application Services (Gateway/Edge Svc)

UI Request (HTTP)

Read Model Microservice

Redis

Overall ApproachCommands & Queries

EventStore

Domain MicroService

RabbitMQ

Commands Queries

Event Sourcing Domain EventsPrecomputed

Results

Page 32: Architecting Microservices in .Net

Web API Controller

Request (HTTP)

Aggregate

Event Handler(s)

Event Store

Domain Micro Service

Command

Message Bus (publish)

Command HandlerCommand(s)

Event Store Repository

Save New Events

Event(s) Event(s)

Page 33: Architecting Microservices in .Net

Web API Controller

Query (HTTP)

Query Handler

Event Handler(s)

Message Bus (subscribe)

Query Micro ServiceEvent(s)

Read Model Persistence

(aka View Store)

Consider splitting here when scaling beyond a single instance to avoid competing consumers

Query

Updates

Page 34: Architecting Microservices in .Net

Specific software & libraries

RabbitMQ + EasyNetQ

EventStore

Redis + StackExchange.Redis

ASP.NET Web API

Page 35: Architecting Microservices in .Net

ImplementationSample code is for inspiration, not duplication

Page 36: Architecting Microservices in .Net

The Micro-café

Inspired by:

Starbucks does not use two phase commithttp://www.enterpriseintegrationpatterns.com/docs/IEEE_Software_Design_2PC.pdf

Page 37: Architecting Microservices in .Net

What are the domain contexts?

Cashier

Barista

Customer

Coffee Shop

What context owns the “product” entity?

Page 38: Architecting Microservices in .Net

Boundaries?

User Story?

As the coffee shop ownerI want to define the products that are offered for saleSo I can set my menu

Use Case?

Manage Products

View products

Create/Update products

Page 39: Architecting Microservices in .Net

Code Walkthrough

Page 40: Architecting Microservices in .Net

Scene Setting

Domain entities are in the application core and updated via methods

Commands/Queries are the adapters and portsfor our services

CQRS – use separate microservices for commands and queries

Page 41: Architecting Microservices in .Net

Admin Microservice

Products

Admin Domain

CommandHandlers

Web API

Repository

Bus Publisher

EventStoreEvent

Handlers

Bus Subscriber

Admin Microservice

Memory Store

Event Store

RabbitMQMemory

Bus

Page 42: Architecting Microservices in .Net

Event Sourcing impacts design

As Event Sourcing is used, domain objects ONLY update their state by processing an event.

Commands do not update state.

Commands cause events to fire.

Useful when replaying events.

Page 43: Architecting Microservices in .Net

public class Product : Aggregate

{

private Product() { }

public Product(Guid id, string name, string description, decimal price)

{

ValidateName(name);

ApplyEvent(new ProductCreated(id, name, description, price));

}

public string Name { get; private set; }

public string Description { get; private set; }

public decimal Price { get; set; }

private void Apply(ProductCreated e)

{

Id = e.Id;

Name = e.Name;

Description = e.Description;

Price = e.Price;

}

Page 44: Architecting Microservices in .Net

Aggregate Base Class

Holds unsaved events

Helper method to reapply events when rehydrating an object from an event stream

Provides a helper method to apply an event of any type and increment the entity’s version property

Page 45: Architecting Microservices in .Net

public abstract class Aggregate

{

public void LoadStateFromHistory(IEnumerable<Event> history)

{

foreach (var e in history) ApplyEvent(e, false);

}

protected internal void ApplyEvent(Event @event)

{

ApplyEvent(@event, true);

}

protected virtual void ApplyEvent(Event @event, bool isNew)

{

this.AsDynamic().Apply(@event);

if (isNew)

{

@event.Version = ++Version;

events.Add(@event);

}

else

{

Version = @event.Version;

}

}

Page 46: Architecting Microservices in .Net

public class Product : Aggregate

{

private void Apply(ProductNameChanged e)

{

Name = e.NewName;

}

public void ChangeName(string newName, int originalVersion)

{

ValidateName(newName);

ValidateVersion(originalVersion);

ApplyEvent(new ProductNameChanged(Id, newName));

}

Page 47: Architecting Microservices in .Net

Port: Command Handlers

Commands do not have to map 1:1 to our internal methods.

Commands (the ports) represent the inbound contract our consumers rely on.

Internal implementation and any domain events are up to us.

Command objects are just property bags.

Page 48: Architecting Microservices in .Net

public class ProductCommandHandlers

{

private readonly IRepository repository;

public ProductCommandHandlers(IRepository repository)

{

this.repository = repository;

}

public void Handle(CreateProduct message)

{

var product = new Products.Domain.Product(message.Id, message.Name,

message.Description, message.Price);

repository.Save(product);

}

Page 49: Architecting Microservices in .Net

Adapters: Command APIs

HTTP Web API

Page 50: Architecting Microservices in .Net

[HttpPost]

public IHttpActionResult Post(CreateProductCommand cmd)

{

if (string.IsNullOrWhiteSpace(cmd.Name))

{

var response = new HttpResponseMessage(HttpStatusCode.Forbidden) { //… }

throw new HttpResponseException(response);

}

try

{

var command = new CreateProduct(Guid.NewGuid(), cmd.Name, cmd.Description, cmd.Price);

handler.Handle(command);

var link = new Uri(string.Format("http://localhost:8181/api/products/{0}", command.Id));

return Created<CreateProduct>(link, command);

}

catch (AggregateNotFoundException) { return NotFound(); }

catch (AggregateDeletedException) { return Conflict(); }

}

Page 51: Architecting Microservices in .Net

Adapters: Database & Message Bus

Repository pattern to encapsulate data access

Event sourcing; persist events not state.

Immediately publish an event on the bus Note: This approach may fail to publish an event

Can be prevented by using Event Store as a pub/sub mechanism

Can also be prevented by publishing to the bus and using a separate microservice to subscribe to and persist events to the EventStore

Personal choice: RabbitMq for ease of use & HA/clustering

Page 52: Architecting Microservices in .Net

public async Task SaveAsync<TAggregate>(TAggregate aggregate) where TAggregate : Aggregate

{

//...

var streamName = AggregateIdToStreamName(aggregate.GetType(), aggregate.Id);

var eventsToPublish = aggregate.GetUncommittedEvents();

//...

if (eventsToSave.Count < WritePageSize)

{

await eventStoreConnection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave);

}

else { //... multiple writes to event store, in a transaction }

if (bus != null)

{

foreach (var e in eventsToPublish) { bus.Publish(e); }

}

aggregate.MarkEventsAsCommitted();

}

Page 53: Architecting Microservices in .Net

Read Models (aka Views)

Subscribe to events

Update their views – i.e. denormalised data

Optimised for querying with minimal I/O

Page 54: Architecting Microservices in .Net

Query Microservice

Product View

Read Model(s)

QueryHandlers

Web API

Repository

Persistence

EventHandlers

Bus Subscriber

Admin Read ModelMicroservice

RedisRabbitMQ

Page 55: Architecting Microservices in .Net

Adapters –Event Subscriptions

Subscribe to messages from the queue at startup

Use Topic Filters to only subscribe to events of interest

Page 56: Architecting Microservices in .Net

var eventMappings = new EventHandlerDiscovery().Scan(productView).Handlers;

var subscriptionName = "admin_readmodel";

var topicFilter1 = "Admin.Common.Events";

var b = RabbitHutch.CreateBus("host=localhost");

b.Subscribe<PublishedMessage>(subscriptionName, m =>

{

Aggregate handler;

var messageType = Type.GetType(m.MessageTypeName);

var handlerFound = eventMappings.TryGetValue(messageType, out handler);

if (handlerFound)

{

var @event = JsonConvert.DeserializeObject(m.SerialisedMessage, messageType);

handler.AsDynamic().ApplyEvent(@event, ((Event)@event).Version);

}

},

q => q.WithTopic(topicFilter1));

Page 57: Architecting Microservices in .Net

Ports:Event Handlers

Views determine events they are interested in

Handle events using the same pattern as the domain objects

Page 58: Architecting Microservices in .Net

public class ProductView : ReadModelAggregate,

IHandle<ProductCreated>,

IHandle<ProductDescriptionChanged>,

IHandle<ProductNameChanged>,

IHandle<ProductPriceChanged>

{

//...

public void Apply(ProductCreated e)

{

var dto = new ProductDto

{

Id = e.Id,

Name = e.Name,

Description = e.Description,

Price = e.Price,

Version = e.Version,

DisplayName = string.Format(displayFormat, e.Name, e.Description),

};

repository.Insert(dto);

}

Page 59: Architecting Microservices in .Net

Read Model Queries

Queries are simply WebAPI methods

Simple lookups of precomputed result(s) in views

Page 60: Architecting Microservices in .Net

RedisRepository

Redis: A key/value store, with fries

Collections stored as ‘sets’

Convention approach to ease implementation Single objects stored using FQ type name

Key = MyApp.TypeName:ID

Value = JSON serialised object

All keys stored in a set, named using FQTN Key = MyApp.TypeNameSet

Values = MyApp.TypeName:ID1, MyApp.TypeName:ID2, etc

Redis can dereference keys in a Set, avoiding N+1 queries.

Page 61: Architecting Microservices in .Net

public IEnumerable<T> GetAll()

{

var get = new RedisValue[] { InstanceName() + "*" };

var result = database.SortAsync(SetName(), sortType: SortType.Alphabetic, by: "nosort", get: get).Result;

var readObjects = result.Select(v => JsonConvert.DeserializeObject<T>(v)).AsEnumerable();

return readObjects;

}

public void Insert(T t)

{

var serialised = JsonConvert.SerializeObject(t);

var key = Key(t.Id);

var transaction = database.CreateTransaction();

transaction.StringSetAsync(key, serialised);

transaction.SetAddAsync(SetName(), t.Id.ToString("N"));

var committed = transaction.ExecuteAsync().Result;

if (!committed)

{

throw new ApplicationException("transaction failed. Now what?");

}

}

Page 62: Architecting Microservices in .Net

Deployment & Docker

Page 63: Architecting Microservices in .Net

Local Development

Before we deploy to <environment />, how do we test our microservices in concert?

Page 64: Architecting Microservices in .Net

Hey, Mister!

I don’t want your Docker Kool-Aid!

That’s cool. You don’t need Docker (or containers).

Always get the latest code you need.

Then manually build & run all of the services on your dev box each time you test.

Use scripting to make it a little less painful.

Side-effect: Encourages a low number of services.

Page 65: Architecting Microservices in .Net

Use service subsets to ease the pain

Use test/mock services.

Only spin up the services you need to test your work, and avoid all the other services that exist

Requires a bit more knowledge around what services to use and what to mock.

Could also use tools like wiremock to intercept and respond to HTTP requests.

Page 66: Architecting Microservices in .Net

Production in a box

Use Docker images and Docker-Compose to automatically build and run environments that match production.

You may be limited by the resources of your dev box (RAM, CPU cores, disk)

Could also use Azure RM templates or Azure Container Services to spin up environments in the cloud. (or the equivalent in AWS)

Page 67: Architecting Microservices in .Net

Containers and versioning

Don’t think about “upgrading” microservices.

Containers are immutable.

You don’t upgrade them; you replace them.

No more binary promotions to prod.

You promote containers to prod.

Have an image repository (e.g. artifactory)

Page 68: Architecting Microservices in .Net

What is the version of the app?

Consumer Driven Contracts reduce the care factor somewhat.

Consider having an environment configuration file

List the version of each microservice that has been tested as part of a “known good” configuration

-- OR --

Ignore versioning and rely on monitoring in production to report problems, and rollback changes quickly

Page 69: Architecting Microservices in .Net

RECAP

1. Why?

2. Architecture

3. Implementation

4. Deployment

Page 70: Architecting Microservices in .Net

Q&A

Page 71: Architecting Microservices in .Net

1-5 August

DDD Sydney thanks our sponsors


Recommended