Monoliths and Microservices Leigh Halliday
The plan •
Intro + definitions
•
Picking the right battle
•
Monolith first
•
Transition to microservices
Quien soy?
The Monolith (majestic or otherwise)
Microservices
PHP
Python
Ruby
Erlang
Technology is not the main determinant of success.
Monoliths
Microservices
So… which?
Important Questions •
What problems are we solving?
•
What are the technological challenges?
•
What are our domains?
•
Where are the boundaries?
“There are other patterns that are less about the code and more about how the code is being written, by whom, and within which organization.” –DHH
https://m.signalvnoise.com/the-majestic-monolith-29166d022228
Decision factors •
Organizational structure or size
•
Different languages/technologies are needed
•
Security or compliance
•
Experience/expertise of team
Monolith first approach •
Start with the Monolith
•
Understand domains, boundaries, challenges
•
Focus on writing organized & disciplined software
•
Break off services as necessary (or don’t)
Why? •
Boundary mistakes are lower in monolith
•
Lower complexity
•
Less infrastructure needed (dev env, infrastructure, monitoring, coordination)
AlpacaLlama You buy it, we ship it.
The model walk
Tracking.last.shipment.order.customer.name
The gate module Shipping def self.schedule(order_id:, address:, weight_kg:, service_level:) Shipment.schedule( order_id: order_id, address: address, weight_kg: weight_kg, service_level: service_level ) end end
Only the necessary •
Only pass what is needed for the job
•
Return as little as possible
•
Treat them as if different codebases
•
Each see each other as a black box
module Purchase class Order < ApplicationRecord def ship! success, response = schedule if success update!(status: 'shipped', shipment_id: response) else fail ShippingError, response end end def schedule Shipping.schedule( order_id: id, address: { postal_code: postal_code }, weight_kg: weight_kg, service_level: 'priority' ) end end end
module Shipping class Shipment < ApplicationRecord def self.schedule(order_id:, address:, weight_kg:, service_level:) shipment = new( order_id: order_id, postal_code: address[:postal_code], weight_kg: weight_kg, service_level: service_level, # ... whatever else ) if shipment.save [true, shipment.id] else [false, shipment.errors.to_h] end end end end
What’s next? Maybe nothing! If it works, why change it?
I want services! •
No worries! There are clean lines in the sand.
•
Easier because of hard work + discipline
•
OK… let’s start!
Nothing in life is free •
What were method calls are now HTTP reqs
•
Everything doubles: apps, databases, gems, servers
•
Coordination between services (deploys can be painful)
•
What about local development?
Conversion steps •
Rails new… (routes, controllers, etc…)
•
All code from Shipping domain moves over
•
Data needs to be migrated
•
Define requests & responses of endpoints
•
Update existing app’s interface to Shipping
The gate changes module Shipping def self.schedule(order_id:, address:, weight_kg:, service_level:) response = ScheduleRequest.new( order_id: order_id, address: address, weight_kg: weight_kg, service_level: service_level ).response if response.success? [true, response.shipment_id] else [false, response.errors] end end end
module Purchase class Order < ApplicationRecord def ship! success, response = schedule if success update!(status: 'shipped', shipment_id: response) else fail ShippingError, response end end def schedule Shipping.schedule( order_id: id, address: { postal_code: postal_code }, weight_kg: weight_kg, service_level: 'priority' ) end end end
What has changed? Nothing.
And testing?
Testing
•
Mock at the gateway
•
Or… VCR: Record & replay HTTP
Code sharing Internal Gem
Thank you! Leigh Halliday www.leighhalliday.com @leighchalliday
Examples: https://github.com/leighhalliday/alpaca_llama_monolith https://github.com/leighhalliday/alpaca_llama_shipping https://github.com/leighhalliday/alpaca_llama_purchase