Microservices
+
Nameko
Welcome everyone, thank you for coming
Talk is about microservices .
And nameko , an open-source framework for writing them in Python
Matt Bennett, head of platform engineering at stealth-company; being filmed
Previously senior engineer at onefinestay , where nameko was born.
How many people know the term microservices?
How many of you did this time two years ago ? Big difference
Microservices is the hot new buzzword , suddenly they seem to be everywhere
History Lesson
November 2014
Martin Fowler & James Lewis published "microservices"
http://martinfowler.com/articles/microservices.html
In Nov 2014, Martin Fowler , James Lewis published "microservices"
Considered to be the seminal paper on the topic
Highly recommend. Accessible, not very long, lots of information
It's also very recent . They didn't invent the term, but gave it a concrete definition , propelled it into vocabulary
At onefinestay, we discovered the paper and realised it described what we had been building.
This was really exciting , now we had a common language to share ideas
Definition
Microservice architectural style is an approach to developing a single application as a suite of small services , each running in its own process and communicating with lightweight mechanisms
For uninitiated , what is the microservices architecture?
Martin Fowler's definition. Read
Monolith vs Microservices
Single process application
e.g. a Django site
Application divided into "services"
Deployed as separate processes
It's helpful to contrast to a monolith - probably the default way to build an app - as a single process
Typical django site good example . Would probably separate your site into "apps", but they run in the same process and memory space
Whereas in microservices, your "apps" become entirely separate programs
In essense: extension of good old fashioned decoupling and encapsulation , applied at the process level .
Forces you to consider the boundaries of services or seams that run through application
Common response to hype: "should be doing that anyway! just good design "; which is true
But with microservices, can't be lazy or bend rules e.g. cross-component import; not there to import
There are also other benefits to using separate processes
Reasons to Adopt Microservices
"Maintainability at Scale"
Primary reason for adopting any software architecture is scale
Or rather, maintainability at scale
Not scale as in serving '00s of millions req/sec. Rather in the complexity of the problem you're trying to solve , the team solving it
There is an analogy for this . Alan Kay (smalltalk, oop) used in 1997 Keynote (video of, was 13 in 1997)
It goes like this: if someone asked you to build a doghouse out of wooden planks and nails...
When society starting building massive structures, like cathedrals... used stone arches
Lightbulb moment: "architecture" etymology, literally application of arches
How can microservices help you achieve maintainability at scale ?
Already said it's about decoupling and isolation. What else?
Independently Deployable
As separate programs , they are independently deployable
Separate release cycles , deployment processes for different part of application
Guardian newspaper: microservices allowed them start using continual delivery in some parts , without risking
Independently Scalable
Separate programs are also independently scalable
Now I do '00s of millions of req/sec
To scale a monolith you have no choice but to deploy another instance
You have to replicate the whole thing
Microservices are much more granular ; therefore composable
e.g. CPU-bound service, deploy more of these to more CPUs without having to drag the rest of the application along
Freedom of Technology
Being good pythonistas , sure we all want to use Python3
Sometimes get stuck using old lib, not updated. Monolith, lowest common denominator
Microservices use most suitable interpreter: py2, py3, pypy. Up to you.
Not too loudly at python conference , but extends to language. Experiment with functional , write service in that lang
Not "Monolithic"
Forgive circular reference; not monolithic
Outside software architecture : big, imposing and impenetrable (think monolith in 2001)
Microservices: small, nimble, easy to grok.
A smaller codebase means shorter on-boarding for new devs
There is lower cognitive overhead . It is inherently more maintainable
Microservices: small, nimble, easy to grok.
A smaller codebase means shorter on-boarding for new devs
There is lower cognitive overhead . It is inherently more maintainable
Conway's Law
organizations which design systems... are constrained to produce designs which are copies of the communication structures of these organizations
And then there's conway's law . ThoughtWorks talk about this a lot.
Who's heard of Conway's Law?
Chap called Melvin Conway, 1968! Not sure any new ideas on software architecture.
Regular 3-tier application (DB, App Logic, UI); likely employ specialists in these fields.
I have worked in this kind of team . As member of middle tier , would talk to app logic peers every day; easy to communicate .
UI folks, different language . Later of friction. That is conway's law in action .
ThoughtWorks recommend: small, multi-discliplinary team ; separate based on natural divisions in the org you're serving
Get an app that better reflects the organisation rather than tech boundaries
Implications of Microservices
"You must be this tall to ride"
Wonderful benefits all well and good . What does it cost ?
Kind of a grown up architecture. Have to have a lot of things in place before you can make it work for you...
If you want to avoid the architectural doghouse
DevOps overhead
"Post CD Architecture"
There is a devops overhead
Increasing by 10 or 20 times things to be built, deployed, looked after
Massive burdon for ops. Only way to cope , leverage automation - tests, deployment, machine management
Another insight from ThoughtWorks: Microservices are "post-CD" .
What they mean is: enabled by automation , without which impossible
I think, this is why microserivices seem to surround us now. Same good ideas of decoupling and isolation, new dimension enabled by devops tech
Domain Knowledge
As well as devops overhead, you must embrace the domain in which you're operating
Sufficient complex app, should be doing anyway; worked places that didn't
Wht I mean is, you have to really understand the business requirements . i.e. the problem you're trying to solve for your org
So you can decide where to draw the lines between services
You can't just build a webapp and tack things on. Microservices force you to do it upfront.
Decentralisation
ACID → BASE
Basically Available
Soft-state
Eventually Consistent
Then there's the decentralised aspect
No longer have a single source of truth like the traditional database layer
Relinquish ACID guarantees, embrase BASE . Which stands for...
Really awkward backronym; good chemistry joke
What this means , is you can't apply transactions across calls to multiple services
Instead you apply it in one place and wait for it to eventually be reflected in the other places
OFS mistake: abstract calendaring service, called by others. Kind of a rookie error. Explicitly delete. Race condition.
Decentralised aspect means you have to think about these things
Complexity
Be aware you're introducing complexity
Collection of microserices fundamentally more complex than a monolith
More moving parts, connected by a network. Inherently less reliable than in-memory calls
In a complex system, failures rarely exactly one reason
Usually commulative effect : network slows down, backlog of requests, combined with recent code change, out of disk space
To mitigate , you need monitoring and telemetry. And analysis of the data that generates
So you can figure out what went wrong (preferably before)
Are Microservices for Me?
Are you fighting a monolith?
Are you ready to build a distributed system?
By now you may be asking yourself whether microservices are for you. If so here are some questions to consider
Codebase large enough that no one person understands it?
Are dev and release cycles slow because of chains of dependent changes?
Do your tests take forever to run?
If so you might be fighting a monolith
If that is the case, are you ready to support a distributed system ?
Are you leveraging automation for tests, deployment + machine mgt?
Do you have suffient monitoring and analysis in place?
If your answers are "yes" and "no", fear not . Maybe you can build a multi-lith
What About A Multi-lith?
"Sliding Scale"
There is a sliding scale between tens of entirely independent services, and a single monolith
You may choose to augment your existing monolith with one or two satelite micoservices : multi-lith (not sure it'll stick)
This way, some of the benefits (diff interpreter, try out CD) without most of the cost
Nameko
A microservices framework that lets service developers concentrate on application logic and encourages testability.
Assuming we're all emboldened and ready to embrace microservices (or a multilith). Talk about nameko
Open source (apache 2) framework designed for writing microservices
It's named after the Japanese mushroom , grow in clusters like this
Kinda like microservices, many individuals making up the larger thing
Botanist friend, "why do they grow like that?". He shrugged and said "'cause there's not mush-rum?". True story . I told him he's a fun-guy
Nameko Concepts
Entrypoints
To interact with a service
Dependencies
For the service to interact with external things
Couple of important concepts that I need to introduce to explain design principals
Entrypoints: how you request something from the service or get it to do something ; its interface/boundary
Dependencies: how the service talks to something external that it may need to communicate with e.g. database, other serv
Example I
class HelloWorld(object):
name = “hello”
@http("GET", "/greet/<string:friend>")
def greet(self, friend):
return “Hello {}!”.format(friend)
https://github.com/mattbennett/nameko-europython-demo
Jump into some code . Code in the following examples is in a repo
Nameko service is written as a python class
Has a name, declared with name attr. Has some business logic in methods
Methods are exposed with an entrypoint . http decorator calls the greet method on GET req to URL.
Example II
class HelloWorld(object):
name = “hello”
cache = CacheClient() # dependency declaration
@rpc
def greet(self, friend):
greeting = self.cache.get(friend)
if greeting is None:
greeting = “Hello {}!”.format(friend) # expensive
self.cache.put(friend, greeting)
return greeting
Let's expand the example. Pretend for a minute string formatting is expensive; cache greetings
Also changed the entrypoint to a Remote Procedure Call impl
First thing to notice : Business logic unchanged by entrypoint change.
We've added logic to deal with the cache, but it's isolated from anything to do with HTTP or RPC
In other words: declarative change, no impact on procedural code in the method
Second thing to point out: line "cache = CacheClient" is declaring a dependency
Dependency Injection
class HelloWorld(object):
name = “hello”
cache = CacheClient()
@rpc
def greet(self, friend):
print(HelloWorld.cache) # DependencyProvider
print(self.cache) # injected dependency
greeting = self.cache.get(friend)
if greeting is None:
greeting = “Hello {}!”.format(friend) # expensive
self.cache.set(friend, greeting)
return greeting
<CacheClient [unbound] at 0x10cbb2750> # DependencyProvider
<memcache.Client object at 0x10cbd25d0> # injected dependency
Dependencies are special. You declare them on the service class
But the class level attribute is different from the instance level attribute that the method sees when it executes
That's because the DependencyProvider, which is the declaration of a dependency , injects the actual dependency at runtime
Hack our method to print, we see that they are different . One is the DependencyProvider. The other is actually a memcache.Client, which the method uses to access the cache
Using dependency injection means only the relevant interface is exposed to the service method (and dev)
All the "plumbing" of that dep like managing a connection pool or handing reconnections is hidden away
Extensible
"Built-in" Extensions
HTTP GET & POST
AMQP RPC
AMQP Pub-Sub
Timer
Websockets (experimental)
Emphasis on entrypoints and dependencies make nameko very extensible
All Entrypoints and DependencyProviders are implemented as "extensions" to nameko
Even the ones that ship with the library (which are included so that it's useful out of the box)
Intention is that you free free build your own , or through wonders of open-source , someone may have already built
This is the full list of built-in extensions
The rpc decorator we've seen is AMQP based RPC implementation for Req-Resp over message bus
There is also publish-subscribe impl for async messaging over AMQP
And timer for cron-like periodic actions and experimental websockets
Worth explaining why we have this AMQP stuff in here
HTTP is a natural starting place for microservices. Lots of great, lightweight web frameworks
HTTP is ubiquitous. Great tooling around API explorers, caching and so on
You are likely to need HTTP on the outside of your services, for clients to communicate with them
But for service-to-service interaction (where you control both sides of comms) probably want something different
In particular, pub-sub is a killer app for microservices
Because all kinds of patterns in distributed systems rely on it
Test Helpers
from mock import call
from nameko.testing.services import worker_factory
hello_svc = worker_factory(HelloWorld)
hello_svc.cache.get.return_value = None
assert hello_svc.greet("Matt") == "Hello Matt!"
assert hello_svc.cache.get.call_args_list == [call("Matt")]
import mockcache
from nameko.testing.services import worker_factory
fake_cache = mockcache.Client()
hello_svc = worker_factory(HelloWorld, cache=fake_cache)
assert hello_svc.greet("Matt") == "Hello Matt!"
assert fake_cache.get("Matt") == "Hello Matt!"
Nameko also ships with some nice test helpers
Already seen how injecting dependencies keeps service interface clean and simple
But it also makes it really easy to pluck them out during testing
Snippet here, using a helper called worker_factory , useful when unit testing services.
Pass it your service class, get an instance with deps replaced by mocks . No real memcache cluster.
Exercise methods, verify mocks called appropriately
worker_factory has another mode of operation where you can provide an alternative dependency.
In this case Mockcache; much nicer interface than a plain mock for this situation; no need to setup return value
There are similar helpers for integration testing -- let you run services with mocked dependencies and disabled entrtpoints
So you can limit the integration points between services
Summary - Microservices
Application divided into services
Running in their own processes
Maintainability at scale
Independently deployable
Independently scalable
Freedom of technology
Team structure
In microservices architecture, split your application into services running as independent processes
Way to achieve maintainability at scale , so you can build cathedrals of software
And comes with a host of other benefits, like freedom of technology, decoupled release cycles, even team structure if you want, for each component part .
Summary - Microservices
"Grown up" architecture. Complex, distributed system
Need to automate your DevOps, monitor, analyse and overall be aware of distributed tradeoffs
But also you can adopt incrementally with a "multi-lith" by adding one or two microservices alongside existing stack
Summary - Nameko
Made for microservices
Encourages you to write clean, highly testable code
Useful "built-in" extensions
Designed to be extended
If you want to go on this microservices adventure , there is an open-source library that can help
Made for writing services
Encourages you to write clean, highly testable code
Several built-in extensions so it's useful out-of-the-box
But is designed to be extended
Want to know more? Read the docs, fork the repo