Skip to content

Instantly share code, notes, and snippets.

@xpepper
Last active September 19, 2020 03:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save xpepper/0486b3f838400fb0e62bb7b563d904cf to your computer and use it in GitHub Desktop.
Save xpepper/0486b3f838400fb0e62bb7b563d904cf to your computer and use it in GitHub Desktop.
Microservices Lessons Learned (https://www.youtube.com/watch?v=mYCdVjMMmck) by Susanne Kaiser

"People try to copy Netflix, but they can only copy what they see. They copy the results, not the process" - A.Cockcroft - AWS

Many case studies about migrating to microservices are about big organizations like Netflix, Google, Zalando, Facebook, Etsy, etc. What about smaller companies?

Each journey is different for every organization: what works for an org does not work for another. No golden-rule.

There are different circumstances:

  • team
  • structure
  • skill-set
  • size

new feature? where do we develop it? feed the monolith? new service?

the story

One team, developing one single codebase, deployed as a whole, with one technology stack

=> new features released slowly

Started with:

  • splitting the big product into several products taking care of different use cases
  • reorganize the "monolithic team" into separate teams, to have:
    • autonomous teams
    • working on different functionality ("solutions") of the big product ("Just Social")

Next was reorganizing the software architecture to reflect this new organizations.

Target:

  • autonomous teams
  • which works on different parts independently
  • develop independently
  • deploy independently
  • scale independently
  • at different speeds

Decomposition strategy

First, identify good candidates for microservices.

You need to have:

  • high cohesion within the service
  • loose coupling between the services

In DDD, "high cohesion" means "related behaviors that change together"

=> A Bounded Context is a "semantic boundary around a part of your domain model describing services that are responsible for well-defined business functions".

First attempt: extracting an existing core functionality ("Co-Existing service from scratch")

First attempt was extracting a core functionality existing inside the monolith into a separate service.

Made too many steps at once!! => late results => long feedback loops => delay gaining experience on the extraction process!

Not good! Lesson learned: when migrating to a microservice architecture it's essential to start learning from the extraction process early on, and get feedback as soon as possible, to tune-in your process and keep improving.

Next candidate, different approach: top-down

  1. First, extracting the user interface as a separate web app and provide a REST API in the monolith.
  2. Second, extract the business logic from the monolith to the new service, sharing the same data storage
  3. Splitting data storage, to allow the service to manage its state exclusively. Made a local copy of other data now owned by the service but needed for its proper working. To keep the data in sync, made use of a message bus to share various types of change events. The monolith publishes an event on the message broker, the service subscribes to this kind of event and receive updates to react to changed data not owned by the service itself

How to prioritize which service to extract?

Lesson learned: start with those ones that

  • easier to extract
  • can gain fast results and early experience with microservices
  • ask "what is the greatest benefit I would gain from the extraction?"
  • services which requires and consumes different amount of resources => gain great advantage from the possibility to deploy it independently in separate env with proper capacity tuned for it

Cross-cutting concerns: Authorization

How to handle authorization? (see also Zalando!) When a new service needs authorization, what should we do?

Two alternatives:

  • feed the monolith: put the code where the authorization is currently handled, in the monolith
  • re-implement the authorization in each new service: arg1!!

Lesson learned: handle cross-cutting concerns early on to avoid feeding the monolith What about then to handle cross-cutting concerns from the beginning, instead of delaying this decision?

idea: "let's introduce a centralized authorization service" => risk to have a distributed monolith! disadvantages of both worlds (monolithic and distributed)

Lesson learned: to avoid the risk of introducing a "distributed monolith" for centralized services, you could provide a common and stable contract that the centralized service owns and every downstream services needs to conform to.

Each new service translates its request to the centralized service to conform to the common contract. That way, the effort is on the new services to conform to the contact, and the centralized service does not need to be changed at all, nor deployed every time. But the common contract should be stable enough!

Service interaction

How services should communicate to each other?

  • Request-driven: services communicating directly over their APIs

    • PRO: The calling service control the request flow
    • CONS: Higher coupling between services (e.g. what if the upstream service takes longer to respond? and is not able to respond?)
  • Event-driven: not communicating directly but via message broker

    • one service publishes events, the other consumes them at its own pace, independently from the other service
    • CONS: introduce a new component which has to be handled
  • Hybrid model: mixing request-driven with event-driven

How to manage shared state and data?

One strategy is the "hybrid model": have the services talk via message broker and fetch the shared data directly to the source with an explicit call (a query) to the service owning the data:

  • CONS: having a remote cross-context query introduce coupling between services

Another strategy is using events not only for notification purposes but also for event-driven state transfer (for data duplication): the service keeps a local copy of some shared data and get updates for changes in the shared data via subscription to a message broker

Source of Truth

In traditional event-driven systems you save your data and actions to a database and you publish events to a message broker to notify other downstream services. The source of truth for you is the database, but for all other downstream services the events are the source of truth => you have two sources of truth!

Issues:

  • So you have to manage the consistency between the two actions of saving to your db and publishing the events to the message broker! (what if you cannot publish an event after you saved the data in your database?)
  • Another issue is that the data model of the db can diverge easily from the data model of the events of the message broker

What to do?

Merge these two models: make events first-class citizen, so that your service itself use events as a source of truth as well all the downstream services. This means using an event-store solution.

A possible solution: Event Streams as a Shared Source of Truth

One tool which helped here was Apache Kafka, which combines the capabilities of:

  • a messaging system (e.g. for notification purposes or for event-driven state transfer)
  • a long-term event storage system (e.g. event store)
  • a streaming platform

Introduced Kafka very early on, initially just for notification purposes. Then they introduces Kafka Streams with great benefits for building a full event-driven solution.

Kafka topic => a logical category of your events (e.g. document topic for document-related update events).

  • The stream is running in the same process of your microservice, so it's easy to integrate in your codebase.
  • Kafka Streams provide an API for joining, filtering, grouping, aggregating streams, transforming streams (map, reduce, etc).
  • Kafka make it easier to have a data-enrichment via a database: see a Kafka Streams as a changelog of state changes of a table, a snapshot of the latest values for each key in the stream => useful to build materialized views as state store

You basically can join two or more streams to build a materialized view ("State Store") to e.g. be accessed by an API to let clients make queries to your service.

Therefore you can avoid to have to keep a local copy of shared data! (see previous solution).

Going back to possible solutions for managing shared data:

  • Events for notification
    • PRO: simple integration
    • CONS: remote queries increase coupling between services
  • Events for data duplication
    • keep a local copy of shared data
    • PRO: eliminate the need for remote queries => reduce coupling
    • CONS: duplicating effort to maintain a local dataset
  • Event streams as a shared source of truth
    • Pushes data to where it's needed
    • PRO: eliminate the need of keeping a local copy => reduce duplicating effort
    • PRO: increase pluggability: no need for the service to do requests to the broker, just create the streams of all the topics that are relevant to the service. No need to take care of a broker anymore.
    • PRO: low barrier to entry for a new service

Infrastructure Complexities as a burden

Infrastructure complexities goes along with introducing microservices. A lot of things to do if you do all of them by yourself! Shifting focus from delivering business value to handling a lot of infrastructure complexities.

Instead:

  • Try to focus on your business values, and build the things that differentiate you from other competitors
  • Offload the things that don't differentiate you (e.g. infrastructure complexities)

Managed Services: Offload by getting common building blocks managed by cloud providers!

Summary

  • Start small (a small candidate that you can easily extract)
  • Handle cross-cutting concerns early: to avoid feeding the monolith or re-implementing cross-cutting concerns on every new service
  • Avoid a distributed monolith: provide a common stable contract for the central authorization service
  • Design event-driven: to help your architecture being easy to evolve
    • consider event streams as a share source of truth
  • Consider managed services to offload infrastructure complexities: try to offload it to cloud providers
  • Be aware of affecting circumstances: ask yourself: "What is blocking us? What is slowing us down?"
    • e.g. try to have all the managing team by your side
  • Remember: each journey is different (and that's totally ok)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment