Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Uncoupling Systems

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Uncoupling Systems

We tackle unique business challenges but are solving similar problems, and all too rarely will we talk through our designs and mistakes. Integrations, external APIs, data-stores - these might not be pure code but we can learn a lot from good object design when scaling our systems.

In this talk we’ll discuss how parts of a distributed systems rely on each other, how data flow can couple and propagate failure, and how common emergent patterns can encapsulate services to be resilient.

This is a journey of close calls, errors, and design smells researched into how a service gets simplified.

Avatar for Jeremy Hanna

Jeremy Hanna

November 13, 2018

Other Decks in Technology

Transcript

  1. class Cataloger URL = "https://www.goodreads.com/" attr_accessor :username, :password def initialize(username,

    password) ... end def import results = get_books if results.code == 200 begin parsed_results = JSON.parse(results.body).dig("data", "results") parsed_results.each do |result| if result["author"] && result["title"] Library::Book.create( ... ) else raise Cataloger::ParseError, "Response missing author or title" end end rescue JSON::ParserError => e raise Cataloger::ParseError, "Unparsable results format" end elsif results.code == 403 raise Cataloger::AuthenticationError else raise StandardError, "Unhandled response from client" end end def get_books RestClient::Request.execute( method: :get, url: URL + "book/list.json", {:Authorization => "Basic #{username}:#{password}" } ) end end
  2. class Cataloger URL = "https://www.goodreads.com/" attr_accessor :username, :password def initialize(username,

    password) ... end def import results = get_books if results.code == 200 begin parsed_results = JSON.parse(results.body).dig("data", "results") parsed_results.each do |result| if result["author"] && result["title"] Library::Book.create( ... ) else raise Cataloger::ParseError, "Response missing author or title" end end rescue JSON::ParserError => e raise Cataloger::ParseError, "Unparsable results format" end elsif results.code == 403 raise Cataloger::AuthenticationError else raise StandardError, "Unhandled response from client" end end def get_books RestClient::Request.execute( method: :get, url: URL + "book/list.json", {:Authorization => "Basic #{username}:#{password}" } ) end end
  3. class Cataloger URL = "https://www.goodreads.com/" attr_accessor :username, :password def initialize(username,

    password) ... end def import results = get_books if results.code == 200 begin parsed_results = JSON.parse(results.body).dig("data", "results") parsed_results.each do |result| if result["author"] && result["title"] Library::Book.create( ... ) else raise Cataloger::ParseError, "Response missing author or title" end end rescue JSON::ParserError => e raise Cataloger::ParseError, "Unparsable results format" end elsif results.code == 403 raise Cataloger::AuthenticationError else raise StandardError, "Unhandled response from client" end end def get_books RestClient::Request.execute( method: :get, url: URL + "book/list.json", {:Authorization => "Basic #{username}:#{password}" } ) end end shape
  4. class Cataloger URL = "https://www.goodreads.com/" attr_accessor :username, :password def initialize(username,

    password) ... end def import results = get_books if results.code == 200 begin parsed_results = JSON.parse(results.body).dig("data", "results") parsed_results.each do |result| if result["author"] && result["title"] Library::Book.create( ... ) else raise Cataloger::ParseError, "Response missing author or title" end end rescue JSON::ParserError => e raise Cataloger::ParseError, "Unparsable results format" end elsif results.code == 403 raise Cataloger::AuthenticationError else raise StandardError, "Unhandled response from client" end end def get_books RestClient::Request.execute( method: :get, url: URL + "book/list.json", {:Authorization => "Basic #{username}:#{password}" } ) end end color
  5. class Cataloger URL = "https://www.goodreads.com/" attr_accessor :username, :password def initialize(username,

    password) ... end def import results = get_books if results.code == 200 begin parsed_results = JSON.parse(results.body).dig("data", "results") parsed_results.each do |result| if result["author"] && result["title"] Library::Book.create( ... ) else raise Cataloger::ParseError, "Response missing author or title" end end rescue JSON::ParserError => e raise Cataloger::ParseError, "Unparsable results format" end elsif results.code == 403 raise Cataloger::AuthenticationError else raise StandardError, "Unhandled response from client" end end def get_books RestClient::Request.execute( method: :get, url: URL + "book/list.json", {:Authorization => "Basic #{username}:#{password}" } ) end end
  6. class CatalogWorker URL = "https://www.goodreads.com/" @queue = :system def self.perform(options

    = {}) books = RestClient::Request.execute( method: :get, url: URL + "book/show.json?id=#{id}", { :Authorization => "Basic #{ENV["CAT_USERNAME"]}:#{ENV["CAT_PASSWORD"]}" }) parsed_results = JSON.parse(results.body).dig("data", "results") parsed_results.each do |result| if result["author"] && result["title"] Library::Book.create( author: response["author"], title: response["title"] ) else raise Cataloger::ParseError, "Response missing author or title" end end end end
  7. not discussing trade-offs ANTI-PATTERN 3: - The network is reliable

    - The network is secure - The network is homogeneous - The network topology does not change - Latency is zero - Bandwidth is infinite - Transport cost is zero
  8. RestClient::Request.execute( method: :get, url: URL + "book/list.json", {:Authorization => "Basic

    #{username}:#{password}" } ) - Availability - Latency - Topology - Bandwidth - Consistency
  9. response = -> () { RestClient::Request.execute( ... ) } 1..10.each

    { |_| foo(response.call) } @response = RestClient::Request.execute( ... ).body Library::Book.find_or_create_from( @response ) Redis.cache.fetch('books', :expires_in => 1.days) do RestClient::Request.execute( ... ).body end
  10. response = -> () { RestClient::Request.execute( ... ) } 1..10.each

    { |_| foo(response.call) } @response = RestClient::Request.execute( ... ).body Library::Book.find_or_create_from( @response ) Redis.cache.fetch('books', :expires_in => 1.days) do RestClient::Request.execute( ... ).body end
  11. - Systems of record and derived data - Encoding -

    Partitioning - Specialized data-stores - Batch and stream processing
  12. your data DESIGN FOR - Communicate through queues - Retryable,

    idempotent workers - Pre-compute work - Identify derived data - Determine consistency-costs
  13. behavior 1. External API 2. Relational Model 3. Business rules

    4. Computations 5. High read, low write
  14. problem: display external reviews • asynchronous • retriable • pre-compute

    information • hard to determine changes • too much data
  15. problem: + post external review • done for client •

    batch process • complex • config errors • “lie” about consistency
  16. add a book and author, problem: then display that to

    a user • straight forward • consistency guarantees • simple locks • joins • limited volume • hotspot queries
  17. add a book and author, problem: then display that to

    a user • keep a lot of the good • Increased • can’t table scan • hotspots • database connections
  18. add a book and author, problem: then display that to

    a user • simple • no joins • no transactions • uptime
  19. add a book and author, problem: then display that to

    a user • transactions • coupled state • no joins
  20. add a book and author, problem: then display that to

    a user • joins • batch • circuit break • consistency cost • complexity ➔ embed stable elements
  21. source title from social problem: media platforms • transactions •

    lock on record • race-condition • lose information
  22. • debugging • source consistency • easy to skew •

    different abstractions source title from social problem: media platforms
  23. source title from social problem: media platforms • recreatable •

    versionable • broadcastable • complexity
  24. ➔ publish an artifact predict a release from problem: author

    publishings • detect anomalies • compare versions
  25. • easy • memory efficient • I/O bound • hotspots

    • querying DB state predict a release from problem: author publishings
  26. • separate stateful from non-stateful functions predict a release from

    problem: author publishings ➔ side-join or broadcast
  27. • reuse • In-sync with display • recreatable but slow

    • no subset queries predict a release from problem: author publishings
  28. • dynamic • query optimized • hard to debug •

    monitoring predict a release from problem: author publishings
  29. For more - Chad Fowler's "From Homogeneous Monolith to Microservices

    Architecture" - David Schmidt’s “10 Tips for Failing Badly at Microservices - Kevlin Henney’s “Old is the New New”