Design and Implementation of Microservices by @samnewman (@NDC_Conferences) [ 4 / 4 ] #testing #versioning

Testing vs. Monitoring-and-Acting

Consider continuously running your main journeys in production! Order a CD every 25 minutes to ensure that things are OK! Don't run destructive tasks though ... ;-) This is an excellent way to identify things that are out of your reach when developing the software, such as changes in a firewall configuration (done by someone else, to resolve an issue elsewhere in your corporation).

Canaries

Make a checklist of what a good service is, deploy a new parallel version and redirect parts of the traffic to the canary, while monitoring its characteristics. If it fulfills the requirements of your checklist, it works and you can safely retire the old version of the service.

UIs

User Interfaces are compositional in nature. We don't want to interact with a system through its distinct parts - we want a cohesive experience. One way of accomplishing this, is to create BFFs, or Backends For Frontends:


Other schools of thought call these services business activity services, business process services or aggregation services, depending on their addition to the ecosystem. This pattern allows you to have client-specific representations of your data - think of it as a server-side presentation model - and can also be used to trigger pre-emptive service calls, using its existing knowledge of a client's consumption patterns (e.g. in an app that swipes from left to right, a backend can pre-emptively collect the information required to render the next view before the user actually swipes and requests it). If you publish mobile apps, you can also use the fact that you own the backend to quickly get a patch out instead of having to wait for days (or sometimes weeks) before the next version of your app with the proper fix gets approved by the app store curators.

Versioning: Managing breaking changes

When working out versioning strategies, there are some options:

  1. Don't create breaking changes! Instead make your contracts larger (and larger...) by adding more optional fields.
  2. Catch the breaking changes, e.g. by using consumer contracts (see the point about Testing and Deployment).
  3. Deploy a new service interface in parallel
    1. Monitor the usage of the interfaces
    2. Maintain two versions until no-one is using the old one
    3. Note that both services must adhere to the same underlying data model
  4. Deploy a new Service in parallel that fulfills the new need while the old one fulfills the old.
As a personal reflection, by thinking about a services conversion patterns, one could go a long way to avoid the hassle with versioning:


In this world, we would never create a direct dependency between our consumer and the service, but always communicate through a service interface. "But we do this today", someone exclaims, to which I answer "yes, in a mechanical sense", arguing that a 1:1 mapping between the service component (the class) and the service interface isn't doing us much good. When painted out side-by-side, it's not terribly hard to argue for the isolated conversion pattern - we can clearly see the benefit of having different types of consumers wanting different responses to the "same" operation - "give me customer information, please". The customer facing website might, for example, want a customer's internal business Id, first and last names and maybe its address. The admin website might want more information... historical performance ... remote ids (foreign keys) related to payments, perhaps ... internal notes. When integrating with a third party, we might not want to expose our internal business id for the customer, but instead we want a computed value... However, if we go back to that first version, it's much harder. We have a service; we have a consumer. How about we just let them talk to eachother? Here's where dependency hell starts, unfortunately. Be mindful of what kind of consumer it is and create an appropriate service contract for that consumer. You'll thank yourself later.

And no, this is not my original idea, but something that really harmonized with me as I studied for my software architecture certification - the idea of isolated conversation pattern.

Back to my notes:
Service discovery helps here. By having an intermediary between the consumer and producer, you can easilly add another producer and redirect calls. Semantic versioning also helps:

Major.Minor.Patch (1.2.45) where Major indicates a breaking change, minor indicates new forwards-compatible changes (i.e. additions) and patches is something that a consumer doesn't generally need to worry about as it's not going to break nor change their experience much.

Or, as Robert Muehsig tweeted (and subsequently wrote a blog post about):
-

 And that was all of my notes for that fateful day in Oslo, June 15th 2015 ;-) Thank you for a great workshop, Mr. Newman and friends!

Comments

Popular posts from this blog

Unit testing your Azure functions - part 2: Queues and Blobs

Unit Testing your Azure Functions

Blob identifiers must be in the format 'container/blob'. #webjobs rocks.