Acknowledging boundaries.

Acknowledging boundaries

Bring back consistency in your microservices architecture

I'm online!

Microservices

show of hands who knows what they are? who's using them?

what's the problem with monoliths? Why add complexity?

Separation of concerns

Avoid duplication of business logic

Smaller codebases

Easier to understand

Smaller teams

Small autonomous teams. New projects can easily be started by cross-functional teams, less time wasted on synchronisation

Independent lifecycles

Independent release schedules, independent deployments. No more "stop the world" releases

Maybe the most important thing to understand about microservices

Compartimentalize failure

If a part of your system is hanging (typically HTTP thread exhaustion, it doesn't bring your whole system down)

Allow different scaling policies

simple stateless part of the system can scale out big stateful part can scale up parts that are harder to scale are stripped down to control their growth

Polyglot tech stacks

Language best suited for the task. Maybe it's a bash script to pilot a C reverse proxy, or a JEE API, or a small scala service

JEE

Main API complex HTTP API & data models

JSE

Simple queue management, no HTTP API will be eventually replaced by something more suited with streams like akka streams

Scala

data management api. good modelisation, good perf

Node Js

small services, was very good at managing push stuff at some point. slowly rewritten as well

Ruby & bash

good for system scripting

Rust

good fit for many things, will partly replace other languages small footprint, easy to deploy

Try new things

some services are less critical, they're the best place to experiment without risk: I've written stuff in go, no I know why I won't use it any more

Polyglot persistence

No central data model

no more central data model

Drop your SQL monolith

Different datastore scaling

multiple data stores with different capabilities / scaling needs and possibilities

Keep your monolith

There are reasons to stay on a monolithic arch

Increased complexity

- more deployments - hidden failure modes

More deployments

Tricky failure modes

Bad network

Can you afford microservices?

Are you ready for microservices ? There are prerequisites

Rapid provisionning

Lots of applications, designed to ease scale out => you need to make room for a new instance very quickly

Basic monitoring

Lots of application, you need to know when one fails.

Rapid application deployment

Easy rollback / restart on failure. Scaling out quickly reduces the degraded quality during traffic surges

12factor

Go farther than the 3 core requirements. 12 factor is completely suited for microservices architectures.

Aparté Clever sur not mandatory

Automate everything

Automate builds

Automate config injection

Automate deployments

Automate whole env provisionning

Don't share state

Each microservice is responsible for its own state no shared access to a DB end of the "SQL store + multiple applications writing in it"

Make state explicit

No persistent local state

Put sessions in a proper DB

Use S3 for files

Use explicit synchronization

don't write your own transaction system in mongodb

Use zk, etcd, consul for transactions

The hard parts

Network is fragile, errors happen

Complexity is still here

Complexity is outside the code

spaghetti
spaghetti
lasagna
lasagna
ravioli
ravioli
wtf
wtf

Know your failure modes

Where will your system break? If service A breaks, what will it take down? If service A is dead slow, what will it make slow?

Know if a call is local or distant

If you feel like you're doing RPC everywhere, then your boundaries are likely bad

Make boundaries explicit

ORMs

"vietnam of computer science"

RMI

let's pretend the network doesn't exist

Hidden complexity == tech debt

if a system seems less complex than its domain, then there's accidental complexity hiding somewhere and you won't find it until it's too late

Hexagonal architecture

Each microservice or DB is an implementation. Clear zoning. Your topology manager does the plugging

Know your topology

You MUST know how services relate to each other, where network is involved. Semantic topology. a docker-compose.yml or a kubernetes JSON config file is not a proper way to define a topology, the same way that a makefile does not describe your business model

Make topology explicit

Make micro-services topology-agnostic

- rabbitMQ instead of HTTP to make a service topology-agnostic

RabbitMQ or HTTP?

Boundaries

Document interactions

- document and specify interactions

Know your protocols

- for HTTP, endpoints and representation structures - for message brokers, exchanges / queue names / topics / … - shared protocols description - at the very least serialization & data types

Serialization

Language-specific serde

Breaks polyglotism

Poor tooling and documentation

Extended attack surface

Turing-complete serde

is bad

XML

nice support for schema and links expensive parsing hard to use, verbose, too powerful (security risk)

JSON

decent support ~everywhere simple enough

JSON schema

representation structure

Swagger / RAML

endpoints integrates document structure API explorer client generation

Avro

serialization system best contendent for a greenfield project

Schema definition language

{"namespace": "example.avro",
 "type": "record",
 "name": "User",
 "fields": [
     { "name": "name"
     , "type": "string"
     },
     { "name": "favorite_number"
     , "type": ["int", "null"]
     },
     { "name": "favorite_color"
     , "type": ["string", "null"]
     }
 ]
}

Compact serialization

clever binary encoding separated schema for better compression

{"name": "Douglas",   "favorite_number": 42, "favorite_color": "red"}
{"name": "Launcelot", "favorite_number": 12, "favorite_color": "blue" }
  Offset  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  0E 44 6F 75 67 6C 61 73 54 06 72 65 64           .DouglasT.red   
00000000  12 4C 61 75 6E 63 65 6C 6F 74 18 08 62 6C 75 65  .Launcelot..blue

Cross-language

support in many languages

Cross-paradigm

deserializes to json codegen not mandatory

Partial reading

const resolver =
  partialSchema
    .createResolver(completeSchema);

const partialMessage =
  resolver
    .fromBuffer(
      completeBuffer,
      resolver,
      true);

Schema evolution

Protocol definition

{
  "namespace": "com.acme",
  "protocol": "HelloWorld",
  "doc": "Protocol Greetings",
  "types": [
    { "name": "Greeting",
      "type": "record",
      "fields": [
        {"name": "message",
         "type": "string"}
      ]
    },
    {"name": "Curse",
     "type": "error",
      "fields": [
        {"name": "message",
         "type": "string"}
      ]
    }
  ],

  "messages": {
    "hello": {
      "doc": "Say hello.",
      "request": [
        {"name": "greeting",
         "type": "Greeting" }],
      "response": "Greeting",
      "errors": ["Curse"]
    }
  }
}

Strong schema

Serialized data is not readable without the schema

Evolution strategies

proper definition of boundaries will make clear when evolutions are local or modify the communication protocol

Stop-the-world

more convenient if you can afford it (small number of parts affected, non critical part, small traffic). Stop everything, update, restart

Two-step

Deploy a forward compatible evolution service by service Remove transition code service by service

Wrap up

Automate

Don't share state

Don't ignore boundaries

Try Clever Cloud

I'm online!