Slide 1

Slide 1 text

Surrounded by Microservices

Slide 2

Slide 2 text

The DVD Rental Company

Slide 3

Slide 3 text

Video on demand

Slide 4

Slide 4 text

Original content

Slide 5

Slide 5 text

Big Shift?

Slide 6

Slide 6 text

Old industry !

Slide 7

Slide 7 text

Remember these?

Slide 8

Slide 8 text

Problem space !

Slide 9

Slide 9 text

Studio apps

Slide 10

Slide 10 text

Starting with a monolith

Slide 11

Slide 11 text

Specialization !

Slide 12

Slide 12 text

Starting fresh !

Slide 13

Slide 13 text

Ruby On Rails !

Slide 14

Slide 14 text

The Active Record Pattern ! Domain Objects " Business Rules # Validations $ Persistence

Slide 15

Slide 15 text

Distributed data !

Slide 16

Slide 16 text

Various data sources ! REST API " JSON API # GRPC $ GraphQL % Local Database

Slide 17

Slide 17 text

"Data that lives in our database today might live in a service tomorrow"

Slide 18

Slide 18 text

Architecture that clearly separates the business logic from any kind of implementation details and protocols

Slide 19

Slide 19 text

Hexagonal Architecture

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Core concepts

Slide 22

Slide 22 text

Entities

Slide 23

Slide 23 text

The repositories

Slide 24

Slide 24 text

The data sources

Slide 25

Slide 25 text

Interactors

Slide 26

Slide 26 text

Implementing business logic

Slide 27

Slide 27 text

Entities module Workflows class ProductionEntity < BaseEntity attribute :id, AttrType::Strict::Integer attribute :title, AttrType::Strict::String attribute :logline, AttrType::Strict::String.optional end end

Slide 28

Slide 28 text

dry-struct

Slide 29

Slide 29 text

Entities class ProductionEntity < BaseEntity attribute :id, AttrType::Strict::Integer attribute :title, AttrType::Strict::String attribute :logline, AttrType::Strict::String.optional end

Slide 30

Slide 30 text

Repositories (Custom DSL) class ProductionRepo < BaseRepo entity_class ProductionEntity def initialize(data_source: MovieProduction) super end def find_by_id(id) wrap(data_source.find_by_id(id)) end def search_by_title(title) wrap(data_source.search_by_title(title)) end end

Slide 31

Slide 31 text

Data Sources class MovieProduction < ApplicationRecord def self.search_by_title(title) where('title LIKE ?', "#{sanitize_sql_like(title)}%") } end end

Slide 32

Slide 32 text

Data Sources class MovieProductionAPI def self.search_by_title(title) SwaggerClient::MovieProductionsApi.advanced_search( search_param: title, headers: { ... }, ... ) end end

Slide 33

Slide 33 text

Data Source Independent # using the default data source repo = ProductionRepo.new # swapping for a REST API data source repo = ProductionRepo.new(data_source: MovieProductionAPI)

Slide 34

Slide 34 text

Interactor gem

Slide 35

Slide 35 text

Interactors module Workflows class OnboardProduction include NetflixInteractor def self.call(production_id:, vertical_id:, repo: ProductionRepo.new) super end def call # validate inputs # is it okay to onboard a production? # cover edge cases # notify others about a production being onboarded end end end

Slide 36

Slide 36 text

Hanami

Slide 37

Slide 37 text

Implementing data sources

Slide 38

Slide 38 text

API Clients

Slide 39

Slide 39 text

Handwritten API Clients

Slide 40

Slide 40 text

Autogenerated API Clients

Slide 41

Slide 41 text

Autogenerated API Clients Swagger Code Generator GRPC JSON API Spec

Slide 42

Slide 42 text

Dealing with errors

Slide 43

Slide 43 text

The network is unreliable

Slide 44

Slide 44 text

Graceful failure

Slide 45

Slide 45 text

Logging downstream requests

Slide 46

Slide 46 text

Metrics

Slide 47

Slide 47 text

Error reporting

Slide 48

Slide 48 text

Errors Must Be Actionable

Slide 49

Slide 49 text

Avoid Alarm Fatigue

Slide 50

Slide 50 text

Setting up thresholds

Slide 51

Slide 51 text

Going straight to the data source

Slide 52

Slide 52 text

Does it scale?

Slide 53

Slide 53 text

Eventual consistency

Slide 54

Slide 54 text

Consistency vs availability

Slide 55

Slide 55 text

"Services should respond in a timely manner"

Slide 56

Slide 56 text

What's in the future?

Slide 57

Slide 57 text

Delaying decisions

Slide 58

Slide 58 text

Project paradox

Slide 59

Slide 59 text

The purpose of a good architecture is to delay decisions. Why? Because when we delay a decision, we have more information when it comes time to make it. - Uncle Bob

Slide 60

Slide 60 text

Postgres? Cassandra? Redis? Elasticsearch?

Slide 61

Slide 61 text

Testing Strategy

Slide 62

Slide 62 text

The test suite must be reliable and fast

Slide 63

Slide 63 text

Rails Testing

Slide 64

Slide 64 text

Testing business logic

Slide 65

Slide 65 text

Dependency Injection

Slide 66

Slide 66 text

Testing with verified doubles describe Workflows::OnboardProduction do let(:production_id) { SecureRandom.uuid } let(:vertical_id) { SecureRandom.uuid } let(:response) do described_class.call(production_id: production_id, vertical_id: vertical_id, repo: repo) end describe 'when a production already exists' do let(:repo) { instance_double(Workflow::ProductionRepo, find_by_id: existing_production) } it 'the response is a failure' do expect(response).to be_a_failure end end ... end

Slide 67

Slide 67 text

Integration specs data sources & end to end

Slide 68

Slide 68 text

A fast and reliable spec suite Finished in 1 minute 2.35 seconds (files took 2.93 seconds to load) 2015 examples, 0 failures

Slide 69

Slide 69 text

Conclusion

Slide 70

Slide 70 text

Separate business logic from implementation details

Slide 71

Slide 71 text

Delay decisions

Slide 72

Slide 72 text

Damir Svrtan Twitter: @DamirSvrtan