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
Data Sources class MovieProductionAPI def self.search_by_title(title) SwaggerClient::MovieProductionsApi.advanced_search( search_param: title, headers: { ... }, ... ) end end
Data Source Independent # using the default data source repo = ProductionRepo.new # swapping for a REST API data source repo = ProductionRepo.new(data_source: MovieProductionAPI)
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
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
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