Architecting BuzzWord Compliant Microservices in .NET Richard Banks @rbanks54 richard-banks.org
Overview 1. Why? 2. Architecture 3. Implementation 4. Deployment
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 onWindows 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 
The Microservices Pitch
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!
OK. More seriously… Greater flexibility & scalability More readily and easily evolvable systems Independently deployable parts Improved technical agility Independent development teams
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
The Reality
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?!
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)
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?
Architecture
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!
Key goals Independent, loosely coupled services Cheap to replace, easy to scale Fault tolerant, version tolerant services
Ports and Adapters (also calledHexagonal orOnionorClean 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
An over simplified view http://www.kennybastani.com/2015/08/polyglot-persistence-spring-cloud-docker.html
How should services communicate? http://www.slideshare.net/adriancockcroft/monitorama-please-no-more/31
KeepComms Simple 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.
Synchronous Comms infers Temporal 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/
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
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 daysWSDLs and XSDs were meant to solve this.Yeah… right. With HTTP APIs, have a look at Pact http://www.infoq.com/articles/consumer-driven-contracts https://github.com/SEEK-Jobs/pact-net https://www.youtube.com/watch?v=SMadH_ALLII
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
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
Implementation Patterns
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.
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.
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.
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.
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
Architecture Pictures It’s not architecture if there’s no boxes and lines!
Application Services (Gateway/Edge Svc) UI Request (HTTP) Read Model Microservice Redis Overall Approach Commands & Queries EventStore Domain MicroService RabbitMQ Commands Queries Event Sourcing Domain Events Precomputed Results
Web API Controller Request (HTTP) Aggregate Event Handler(s) Event Store Domain Micro Service Command Message Bus (publish) Command Handler Command(s) Event Store Repository Save New Events Event(s) Event(s)
Web API Controller Query (HTTP) Query Handler Event Handler(s) Message Bus (subscribe) Query Micro Service Event(s) Read Model Persistence (akaView Store) Consider splitting here when scaling beyond a single instance to avoid competing consumers Query Updates
Specific software & libraries RabbitMQ + EasyNetQ EventStore Redis + StackExchange.Redis ASP.NETWeb API
Implementation Sample code is for inspiration, not duplication
The Micro- café Inspired by: Starbucks does not use two phase commit http://www.enterpriseintegrationpatterns.com/docs/IEEE_Software_Design_2PC.pdf
What are the domain contexts?  Cashier  Barista  Customer  Coffee Shop  What context owns the “product” entity?
Boundaries? User Story? As the coffee shop owner I want to define the products that are offered for sale So I can set my menu Use Case? Manage Products  View products  Create/Update products
CodeWalkthrough
SceneSetting Domain entities are in the application core and updated via methods Commands/Queries are the adapters and ports for our services CQRS – use separate microservices for commands and queries
Admin Microservice Products Admin Domain Command Handlers Web API Repository Bus Publisher Event StoreEvent Handlers Bus Subscriber Admin Microservice Memory Store Event Store RabbitMQ Memory Bus
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.
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; }
Aggregate BaseClass 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
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; } }
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)); }
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.
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); }
Adapters: Command APIs HTTPWeb API
[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(); } }
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
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(); }
Read Models (akaViews) Subscribe to events Update their views – i.e. denormalised data Optimised for querying with minimal I/O
Query Microservice Product View Read Model(s) Query Handlers Web API Repository Persistence Event Handlers Bus Subscriber Admin Read Model Microservice RedisRabbitMQ
Adapters – Event Subscriptions Subscribe to messages from the queue at startup UseTopic Filters to only subscribe to events of interest
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));
Ports: Event Handlers Views determine events they are interested in Handle events using the same pattern as the domain objects
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); }
Read Model Queries Queries are simplyWebAPI methods Simple lookups of precomputed result(s) in views
Redis Repository 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.
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?"); } }
Deployment & Docker
Local Development Before we deploy to <environment />, how do we test our microservices in concert?
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.
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.
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)
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)
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
RECAP 1. Why? 2. Architecture 3. Implementation 4. Deployment
Q&A
1-5 August DDD Sydney thanks our sponsors

Architecting Microservices in .Net

  • 1.
    Architecting BuzzWord Compliant Microservices in.NET Richard Banks @rbanks54 richard-banks.org
  • 2.
    Overview 1. Why? 2. Architecture 3.Implementation 4. Deployment
  • 3.
    Preamble As an industrywe’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 onWindows 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 
  • 4.
  • 5.
    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!
  • 6.
    OK. More seriously… Greater flexibility& scalability More readily and easily evolvable systems Independently deployable parts Improved technical agility Independent development teams
  • 7.
    A few more reasons? Sure! Resilience.Afailure 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
  • 8.
  • 9.
    WTF!OMG! Gah! <update resume /> Isn’tthis 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?!
  • 10.
    Why? Distributed systems arehard 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)
  • 11.
    Other things to dealwith 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?
  • 12.
  • 13.
    A quick reminder Architecture isn’tabout 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!
  • 14.
    Key goals Independent, looselycoupled services Cheap to replace, easy to scale Fault tolerant, version tolerant services
  • 15.
  • 16.
  • 17.
  • 18.
    KeepComms Simple 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.
  • 19.
    Synchronous Comms infers Temporal Coupling If yourservices 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/
  • 20.
    Identify your business transactions One userrequest 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
  • 21.
    Evolvable APIs via Consumer Driven Contracts A conceptfrom 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 daysWSDLs and XSDs were meant to solve this.Yeah… right. With HTTP APIs, have a look at Pact http://www.infoq.com/articles/consumer-driven-contracts https://github.com/SEEK-Jobs/pact-net https://www.youtube.com/watch?v=SMadH_ALLII
  • 22.
    Loose Coupling and Service Discovery Loose couplingimplies 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
  • 23.
    Loose Coupling implies Data Duplication For services tobe independent: they cannot rely on another service being up at the same time (temporal coupling), and they should cache any external data they need
  • 24.
  • 25.
    Design Patterns & Components Domain DrivenDesign – 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.
  • 26.
    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.
  • 27.
    Event Sourcing. Really? Yes, really :-) Whena 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.
  • 28.
    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.
  • 29.
    Rule of thumb for sizing microservices Havea 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
  • 30.
    Architecture Pictures It’s notarchitecture if there’s no boxes and lines!
  • 31.
    Application Services (Gateway/EdgeSvc) UI Request (HTTP) Read Model Microservice Redis Overall Approach Commands & Queries EventStore Domain MicroService RabbitMQ Commands Queries Event Sourcing Domain Events Precomputed Results
  • 32.
    Web API Controller Request(HTTP) Aggregate Event Handler(s) Event Store Domain Micro Service Command Message Bus (publish) Command Handler Command(s) Event Store Repository Save New Events Event(s) Event(s)
  • 33.
    Web API Controller Query(HTTP) Query Handler Event Handler(s) Message Bus (subscribe) Query Micro Service Event(s) Read Model Persistence (akaView Store) Consider splitting here when scaling beyond a single instance to avoid competing consumers Query Updates
  • 34.
    Specific software & libraries RabbitMQ +EasyNetQ EventStore Redis + StackExchange.Redis ASP.NETWeb API
  • 35.
    Implementation Sample code isfor inspiration, not duplication
  • 36.
    The Micro- café Inspired by: Starbucksdoes not use two phase commit http://www.enterpriseintegrationpatterns.com/docs/IEEE_Software_Design_2PC.pdf
  • 37.
    What are the domain contexts? Cashier  Barista  Customer  Coffee Shop  What context owns the “product” entity?
  • 38.
    Boundaries? User Story? As thecoffee shop owner I want to define the products that are offered for sale So I can set my menu Use Case? Manage Products  View products  Create/Update products
  • 39.
  • 40.
    SceneSetting Domain entities arein the application core and updated via methods Commands/Queries are the adapters and ports for our services CQRS – use separate microservices for commands and queries
  • 41.
    Admin Microservice Products Admin Domain Command Handlers Web API Repository BusPublisher Event StoreEvent Handlers Bus Subscriber Admin Microservice Memory Store Event Store RabbitMQ Memory Bus
  • 42.
    Event Sourcing impacts design As Event Sourcingis 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.
  • 43.
    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; }
  • 44.
    Aggregate BaseClass Holds unsaved events Helpermethod 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
  • 45.
    public abstract classAggregate { 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; } }
  • 46.
    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)); }
  • 47.
    Port: Command Handlers Commands do nothave 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.
  • 48.
    public class ProductCommandHandlers { privatereadonly 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); }
  • 49.
  • 50.
    [HttpPost] public IHttpActionResult Post(CreateProductCommandcmd) { 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(); } }
  • 51.
    Adapters: Database & Message Bus Repositorypattern 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
  • 52.
    public async TaskSaveAsync<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(); }
  • 53.
    Read Models (akaViews) Subscribe toevents Update their views – i.e. denormalised data Optimised for querying with minimal I/O
  • 54.
  • 55.
    Adapters – Event Subscriptions Subscribe tomessages from the queue at startup UseTopic Filters to only subscribe to events of interest
  • 56.
    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));
  • 57.
    Ports: Event Handlers Views determine eventsthey are interested in Handle events using the same pattern as the domain objects
  • 58.
    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); }
  • 59.
    Read Model Queries Queries aresimplyWebAPI methods Simple lookups of precomputed result(s) in views
  • 60.
    Redis Repository Redis: A key/valuestore, 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.
  • 61.
    public IEnumerable<T> GetAll() { varget = 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?"); } }
  • 62.
  • 63.
    Local Development Before we deployto <environment />, how do we test our microservices in concert?
  • 64.
    Hey, Mister! I don’twant 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.
  • 65.
    Use service subsets to easethe 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.
  • 66.
    Production in a box UseDocker 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)
  • 67.
    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)
  • 68.
    What is the versionof 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
  • 69.
    RECAP 1. Why? 2. Architecture 3.Implementation 4. Deployment
  • 70.
  • 71.
    1-5 August DDD Sydneythanks our sponsors