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
- DevOps automation
- Monitoring
- Analysis
- Awareness
Try it out in a multi-lith
- "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