├── .gitignore ├── _config.yml ├── ruby ├── examples │ ├── .rspec │ ├── Gemfile │ ├── Makefile │ ├── Dockerfile │ ├── README.md │ ├── lib │ │ ├── orders │ │ │ ├── domain │ │ │ │ └── discountable_order.rb │ │ │ ├── gateway │ │ │ │ └── order.rb │ │ │ └── use_case │ │ │ │ └── apply_order_discount.rb │ │ └── users │ │ │ ├── gateway │ │ │ └── user.rb │ │ │ └── use_case │ │ │ └── register_user.rb │ ├── spec │ │ ├── lib │ │ │ ├── orders │ │ │ │ ├── domain │ │ │ │ │ └── discountable_order.rb │ │ │ │ ├── gateway │ │ │ │ │ └── order_spec.rb │ │ │ │ └── use_case │ │ │ │ │ └── apply_order_discount_spec.rb │ │ │ └── users │ │ │ │ ├── use_case │ │ │ │ └── register_user_spec.rb │ │ │ │ └── gateway │ │ │ │ └── user_spec.rb │ │ ├── controllers │ │ │ └── user_registration_controller_spec.rb │ │ └── spec_helper.rb │ └── app │ │ └── controllers │ │ └── user_registration_controller.rb ├── Domain.md ├── clean-architecture.png ├── Gateway.md ├── RSpec.md ├── README.md └── UseCases.md ├── kotlin ├── Domain.md ├── Gateway.md ├── README.md └── UseCases.md ├── gateway.md ├── domain.md ├── learn ├── ATDD.md ├── the-mindset.md ├── basics │ ├── use-cases-organise.md │ ├── fake-gateways.md │ ├── constructors-for-collaborators.md │ └── start-with-acceptance.md ├── practicality.md └── README.md ├── bounded_contexts.md ├── use_case.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /ruby/examples/.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /ruby/Domain.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture Ruby: Domain 3 | --- 4 | 5 | # Domain 6 | 7 | -------------------------------------------------------------------------------- /kotlin/Domain.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture: Kotlin Domain 3 | --- 4 | 5 | # Domain 6 | 7 | -------------------------------------------------------------------------------- /ruby/clean-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madetech/clean-architecture/HEAD/ruby/clean-architecture.png -------------------------------------------------------------------------------- /ruby/examples/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'activemodel' 5 | gem 'actionview' 6 | -------------------------------------------------------------------------------- /ruby/examples/Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | build: 4 | docker build -t app . 5 | 6 | test: build 7 | docker run -t app rspec 8 | -------------------------------------------------------------------------------- /ruby/examples/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.4.0 2 | WORKDIR /clean 3 | COPY Gemfile* /clean 4 | RUN bundle check || bundle install 5 | COPY . /clean 6 | -------------------------------------------------------------------------------- /ruby/examples/README.md: -------------------------------------------------------------------------------- 1 | # Ruby Clean Architecture Examples 2 | 3 | ## Prerequisites 4 | 5 | - Install docker 6 | 7 | ## Run tests 8 | 9 | ``` 10 | make test 11 | ``` 12 | -------------------------------------------------------------------------------- /kotlin/Gateway.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture: Kotlin Gateway 3 | --- 4 | 5 | # Gateway 6 | 7 | Contains IO adapters (e.g. files, database or API calls) 8 | These construct Domain objects for use by Use Cases, and, save Domain objects given to it -------------------------------------------------------------------------------- /ruby/Gateway.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture Ruby: Gateway 3 | --- 4 | 5 | # Gateway 6 | 7 | Contains IO adapters (e.g. files, database or API calls) 8 | These construct Domain objects for use by Use Cases, and, save Domain objects given to it -------------------------------------------------------------------------------- /ruby/examples/lib/orders/domain/discountable_order.rb: -------------------------------------------------------------------------------- 1 | module Orders 2 | module Domain 3 | class DiscountableOrder < Struct.new(:discount) 4 | def has_discount? 5 | discount > 0 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /ruby/examples/lib/users/gateway/user.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | module Gateway 3 | class User 4 | def create_user(attributes) 5 | order = Spree::User.create(attributes) 6 | order.errors 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /ruby/examples/spec/lib/orders/domain/discountable_order.rb: -------------------------------------------------------------------------------- 1 | describe Orders::Domain::DiscountableOrder do 2 | context 'when discount > 0' do 3 | before { subject.discount = 5 } 4 | it { is_expected.to have_discount } 5 | end 6 | 7 | context 'when discount is 0' do 8 | before { subject.discount = 0 } 9 | it { is_expected.to_not have_discount } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /ruby/examples/lib/orders/gateway/order.rb: -------------------------------------------------------------------------------- 1 | module Orders 2 | module Gateway 3 | class Order 4 | def find_order_by_id(id) 5 | order = Spree::Order.find(id) 6 | Domain::DiscountableOrder.new(order.discount) 7 | end 8 | 9 | def save_order_discount(id, order) 10 | Spree::Order.find(id).update!(discount: order.discount) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /ruby/RSpec.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture Ruby: RSpec 3 | --- 4 | 5 | # RSpec ATDD Structure 6 | 7 | ## spec/acceptance 8 | 9 | Contains end-to-end acceptance specs, without the Web Delivery mechanism 10 | These specs call the interface that the Web Delivery mechanism uses 11 | 12 | ## spec/unit 13 | 14 | Contains unit specs 15 | 16 | ## spec/fixtures 17 | 18 | Contains raw fixtures 19 | 20 | ## spec/test_doubles 21 | 22 | Contains "complex" test doubles -------------------------------------------------------------------------------- /ruby/examples/lib/users/use_case/register_user.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | module UseCase 3 | class RegisterUser 4 | Request = Struct.new(:email, :name, :password) 5 | Response = Struct.new(:errors) 6 | 7 | attr_reader :gateway 8 | 9 | def initialize(gateway: Gateway::User.new) 10 | @gateway = gateway 11 | end 12 | 13 | def register(request) 14 | errors = gateway.create_user(request.to_h) 15 | Response.new(errors) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /ruby/examples/spec/lib/users/use_case/register_user_spec.rb: -------------------------------------------------------------------------------- 1 | describe Users::UseCase::RegisterUser do 2 | subject do 3 | register_user = described_class.new(gateway: FakeGateway.new) 4 | register_user.register(Users::UseCase::RegisterUser::Request.new(email)) 5 | end 6 | 7 | context 'when user created without errors' do 8 | let(:email) { 'a@a.com' } 9 | it { expect(subject.errors).to be_empty } 10 | end 11 | 12 | context 'when user has errors' do 13 | let(:email) { nil } 14 | it { expect(subject.errors).to_not be_empty } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ruby/examples/spec/lib/users/gateway/user_spec.rb: -------------------------------------------------------------------------------- 1 | describe Users::Gateway::User do 2 | context 'when create user' do 3 | subject do 4 | described_class.new.create_user( 5 | email: email, 6 | name: 'Luke', 7 | password: 'pass' 8 | ) 9 | end 10 | 11 | context 'with valid email' do 12 | let(:email) { 'luke@cool.com' } 13 | it { is_expected.to be_empty } 14 | end 15 | 16 | context 'with missing email' do 17 | let(:email) { nil } 18 | it { is_expected.to_not be_empty } 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /ruby/examples/spec/lib/orders/gateway/order_spec.rb: -------------------------------------------------------------------------------- 1 | describe Orders::Gateway::Order do 2 | context 'when finding order by id' do 3 | subject { described_class.new.find_order_by_id(1) } 4 | 5 | it { is_expected.to be_a(Orders::Domain::DiscountableOrder) } 6 | it { is_expected.to have_discount } 7 | 8 | it 'should have correct discount' do 9 | expect(subject.discount).to eq(10) 10 | end 11 | end 12 | 13 | context 'when saving order discount' do 14 | subject { described_class.new.save_order_discount(1, Orders::Domain::DiscountableOrder.new(10)) } 15 | it { is_expected.to eq(true) } 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ruby/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture: Ruby 3 | --- 4 | 5 | # Made Tech Flavour Clean Architecture: Ruby 6 | 7 | ![Ruby Clean Architecture](clean-architecture.png) 8 | 9 | ## Testing 10 | * [RSpec](RSpec.md) (RSpec specific test layout) 11 | 12 | ## Production Code 13 | ``` (lib|src)//**/*.rb``` 14 | 15 | Customer code should be housed within a Client namespace e.g. ```AcmeIndustries::Financial::UseCase::CreateInvoice``` 16 | 17 | Non-customer specfic code should be housed within a MadeTech namespace e.g. ```MadeTech::Authentication::UseCase::Login``` 18 | 19 | * [Use Cases](UseCases.md) use_case/ 20 | * [Domain](Domain.md) domain/ 21 | * [Gateway](Gateway.md) gateway/ 22 | -------------------------------------------------------------------------------- /ruby/examples/spec/lib/orders/use_case/apply_order_discount_spec.rb: -------------------------------------------------------------------------------- 1 | describe Orders::UseCase::ApplyOrderDiscount do 2 | let(:gateway) { double(find_order_by_id: order, save_order_discount: true) } 3 | subject { described_class.new(gateway: gateway).apply(double(id: 1)) } 4 | 5 | context 'when order has not existing discounts' do 6 | let(:order) { Orders::Domain::DiscountableOrder.new(0) } 7 | it { is_expected.to have_discount } 8 | it { expect(subject.discount).to eq(10) } 9 | end 10 | 11 | context 'when order has existing discounts' do 12 | let(:order) { Orders::Domain::DiscountableOrder.new(5) } 13 | it { is_expected.to have_discount } 14 | it { expect(subject.discount).to eq(5) } 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ruby/UseCases.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture Ruby: Use Cases 3 | --- 4 | 5 | # Use Cases 6 | 7 | Standard Directory: use_case/ 8 | 9 | ## Libraries 10 | 11 | * [Deject](https://github.com/JoshCheek/deject) 12 | 13 | ## Example 14 | 15 | ```ruby 16 | module AcmeIndustries 17 | module Widget 18 | module UseCase 19 | class WidgetsPerFooBarReport 20 | Deject self, :widget_gateway 21 | 22 | def execute(from_date:, to_date:) # receive a hash 23 | # widgets is a collection of Domain::Widget objects 24 | widgets = widget_gateway.all 25 | 26 | # secret sauce here 27 | 28 | {} # return a hash 29 | end 30 | end 31 | end 32 | end 33 | end 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /gateway.md: -------------------------------------------------------------------------------- 1 | # Gateway 2 | 3 | The responsibility of a gateway is to adapt an IO mechanism for your [Use Cases](use_case.md). 4 | 5 | Usually, a gateway will be the adapter between a data source (e.g. Postgresql) and a particular [Domain](domain.md) object (e.g. Order) 6 | 7 | In Object-Oriented languages a gateways are usually a class which implements an interface. 8 | 9 | IO is could be anything external to your application e.g. files, database or even HTTP API calls 10 | 11 | It is the responsibility of Gateways to (one or more of): 12 | 13 | * Construct Domain objects by reading from the I/O source 14 | * Accept Domain objects to be written to the I/O source 15 | 16 | Gateways are I/O source specific e.g. ActiveRecord, Sequel, MySQL, Paypal, FileSystem, RethinkDB 17 | -------------------------------------------------------------------------------- /ruby/examples/lib/orders/use_case/apply_order_discount.rb: -------------------------------------------------------------------------------- 1 | module Orders 2 | module UseCase 3 | class ApplyOrderDiscount 4 | Request = Struct.new(:id) 5 | Response = Struct.new(:discount, :has_discount?) 6 | 7 | attr_reader :gateway 8 | 9 | def initialize(gateway: Gateway::Order.new) 10 | @gateway = gateway 11 | end 12 | 13 | def apply(request) 14 | order = gateway.find_order_by_id(request.id) 15 | 16 | unless order.has_discount? 17 | order.discount = discount_amount 18 | gateway.save_order_discount(request.id, order) 19 | end 20 | 21 | Response.new(order.discount, order.has_discount?) 22 | end 23 | 24 | private 25 | 26 | def discount_amount 27 | 10 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /ruby/examples/spec/controllers/user_registration_controller_spec.rb: -------------------------------------------------------------------------------- 1 | describe UserRegistrationController do 2 | let(:email) { 'a@a.com' } 3 | 4 | before do 5 | subject.params = { email: email } 6 | subject.create 7 | end 8 | 9 | context 'when using with form helper' do 10 | let(:html) { '' } 11 | 12 | before do 13 | html << View.new.form_for(subject.user_instance_var) do |f| 14 | html << f.email_field(:email) 15 | html << f.submit 16 | end 17 | end 18 | 19 | it 'should work with Rails forms correctly' do 20 | expect(html).to include(email) 21 | end 22 | end 23 | 24 | context 'when email missing' do 25 | let(:email) { nil } 26 | 27 | it 'should contain Rails errors' do 28 | expect(subject.user_instance_var.errors.messages).to include(email: ['can\'t be blank']) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /ruby/examples/app/controllers/user_registration_controller.rb: -------------------------------------------------------------------------------- 1 | class UserRegistrationController < ApplicationController 2 | def new 3 | @user = UserRegistration.new 4 | end 5 | 6 | def create 7 | request = Users::UseCase::RegisterUser::Request.new(*user_params) 8 | response = Users::UseCase::RegisterUser.new.register(request) 9 | @user = UserRegistration.new(request, response) 10 | end 11 | 12 | private 13 | 14 | def user_params 15 | params.require(:user).values_at(:email, :name, :password) 16 | end 17 | 18 | class UserRegistration 19 | include ActiveModel::Model 20 | 21 | attr_reader *Users::UseCase::RegisterUser::Request.members 22 | attr_reader *Users::UseCase::RegisterUser::Response.members 23 | 24 | def initialize(request = nil, response = nil) 25 | @email = request.try(:email) 26 | @name = request.try(:name) 27 | @password = request.try(:password) 28 | @errors = response.try(:errors) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /domain.md: -------------------------------------------------------------------------------- 1 | # Domain 2 | 3 | The purpose of Domain objects are to model the domain in a "data storage agnostic way". 4 | The key here that there is an impedance-mismatch between Data Structures and Objects. 5 | 6 | Since databases store Data Structures, not Objects with behaviour, we should rely on Gateways to do this conversion for us. 7 | 8 | The challenge is determining what behaviours lie within Domain objects, and what behaviours lie within Use Cases. 9 | 10 | A good rule of thumb is that behaviours within Domain objects *must be valid for all Use Cases across the system.* 11 | 12 | It is cheaper to specialise Use Cases, resulting in an anemic domain model, then evolve the systems towards generalisations once patterns emerge. 13 | 14 | In this code we have a simple Domain object 15 | 16 | ```ruby 17 | class Light 18 | attr_reader :brightness 19 | 20 | def initialize(brightness:) 21 | @brightness = brightness 22 | end 23 | end 24 | ``` 25 | 26 | ## Alternative Names 27 | 28 | * Entities 29 | 30 | We stick to the name "Domain" 31 | -------------------------------------------------------------------------------- /kotlin/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture: Kotlin 3 | --- 4 | 5 | # Made Tech Flavour Clean Architecture: Kotlin 6 | 7 | Example project: [MLD Klean Architecture (Continuous Feedback)](https://github.com/madetech/dojos/tree/67eb97d93135ae0fc54bada70e2d2656f7873b88/mld-klean-architecture) 8 | 9 | ## [Testing](../learn/ATDD.md) 10 | ### Spek 11 | 12 | #### Acceptance Test 13 | An executing [example can be found here](https://github.com/madetech/dojos/blob/67eb97d93135ae0fc54bada70e2d2656f7873b88/mld-klean-architecture/src/test/kotlin/io/continuousfeedback/core/test/acceptance/TeamNotificationsSpec.kt). 14 | 15 | #### Unit Test 16 | An executing [example can be found here](https://github.com/madetech/dojos/blob/67eb97d93135ae0fc54bada70e2d2656f7873b88/mld-klean-architecture/src/test/kotlin/io/continuousfeedback/core/test/unit/CreateTeamMemberSpec.kt). 17 | 18 | 19 | ## Production Code 20 | 21 | Customer code should be housed within a Client package e.g. ```com.acmeindustries.widget``` 22 | 23 | Non-customer specfic code should be housed within a MadeTech namespace e.g. ```com.madetech.authentication``` 24 | 25 | * [Use Cases](UseCases.md) use_case/ 26 | * [Domain](Domain.md) domain/ 27 | * [Gateway](Gateway.md) gateway/ 28 | -------------------------------------------------------------------------------- /learn/ATDD.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture & ATDD 3 | --- 4 | 5 | # ATDD 6 | 7 | ## Acceptance Testing 8 | 9 | The purpose of acceptance testing is to test an entire system via it's boundary only. 10 | It serves as a *stand-in*, and documentation for the implementation of the future UI of your system. 11 | Anything that you are coupled to in your acceptance tests, you are also going to be coupled to in your UI. 12 | 13 | This means an acceptance test, cannot depend on the following types of objects: 14 | 15 | - Domain objects 16 | - Gateways 17 | 18 | It must only depend on the *Boundary* of your system. 19 | 20 | **The purpose of acceptance tests** is to measure success towards the goal of meeting the customer's needs, and reduces the 21 | occurrence of gold plating 22 | 23 | ## Unit Testing 24 | 25 | Unit Tests are able to break these rules of acceptance tests. 26 | It is meant to serve as documentation of the behaviour of lower level components. 27 | Since these tests are lower level it is possible to test-drive the system into performing every possible permutation of behaviour under a test situation. 28 | 29 | This gives the property of [(semantic stability)](https://www.madetech.com/blog/semantically-stable-test-suites) . 30 | 31 | -------------------------------------------------------------------------------- /learn/the-mindset.md: -------------------------------------------------------------------------------- 1 | # Clean Architecture Mindset 2 | 3 | ## Values 4 | 5 | Clean Architecture works best when all programmers share the same mindset, or at the very least understand and apply its mindset. 6 | 7 | *We are uncovering better ways of writing code and architecting software by doing it and helping others do it. Through this work we have come to value:* 8 | 9 | **Expressing the domain simply** over expressing the domain via tools and frameworks 10 | 11 | **Executable documentation** over non-executable documentation 12 | 13 | **Customer usage, deferring technology choices** over fitting customer usage into technology choices 14 | 15 | **Delivering value with low cost of change** over delivering hard to change value sooner 16 | 17 | *That is, while there is value in the items on 18 | the right, we value the items on the left more.* 19 | 20 | ## Principles 21 | 22 | * Code rots and becomes a big ball of mud when programmers fear changing code 23 | * Applying a sound strategy for preventing the introduction of defects, such as TDD, unpins eliminating fear. [(semantic stability)](https://www.madetech.com/blog/semantically-stable-test-suites) 24 | * Refactoring can occur at any time when there is no fear 25 | * When the team understanding of the domain improves, keeping the in-code model of the domain up to date is important. 26 | * The SOLID and package principles provide a guide to aid good software design 27 | -------------------------------------------------------------------------------- /learn/basics/use-cases-organise.md: -------------------------------------------------------------------------------- 1 | # Use Cases organise your code 2 | 3 | Over time many software systems accumulate a large list of use cases that can be used either by end users through the UI or be composed together. 4 | 5 | Clean Architecture seeks to make this list of use cases easy to navigate. 6 | 7 | ## One Use Case per Class 8 | 9 | As a general rule, you should create one class to house one use case. 10 | 11 | One reason to do this is to create _isolation_ - it is harder to break other use cases if the code is physically located in separate files. 12 | We value this isolation and decoupling to avoid the design smell of _fragility_. 13 | 14 | Another reason is to make it easier to name your classes - _JournalManager_ is a broadly useless name which will require inspecting the contents of the class to learn it's intent. 15 | Whereas _ViewJournal_ is a discrete, descriptive name that will enable you to decide _from the name alone_ if it is relevant to your current goal. 16 | 17 | ## Use the command pattern 18 | 19 | We expose a single method called `execute` which takes a simple data structure and returns a simple data structure. 20 | 21 | In ruby, we use hashes `{}`. 22 | 23 | ```ruby 24 | class ViewJournal 25 | def execute(request) 26 | {} 27 | end 28 | end 29 | ``` 30 | 31 | ## Responsibility 32 | 33 | Use cases divide your code base into chunks of business logic that should be responsible to one (and only one) actor. 34 | 35 | For example, in an eCommerce system you may have identified the following actors: 36 | 37 | - The Customer 38 | - The Payer 39 | - The Financial Team 40 | - The Warehouse 41 | - The Customer Service Team 42 | 43 | -------------------------------------------------------------------------------- /bounded_contexts.md: -------------------------------------------------------------------------------- 1 | # Bounded Contexts 2 | 3 | Once the SOLID and Package principles are understood, it is important to understand the role that bounded contexts play. 4 | 5 | ## Explicit 6 | 7 | When creating explicit bounded contexts, package principles and the cost of creating a separate package apply. 8 | 9 | Explicit bounded contexts use a language or tooling-level feature to encode separation these could include using different repositories, separate microservice, maven multi-modules, gems or even .NET assemblies. 10 | The benefit of explicit bounded contexts is that they promote clear separation (some methods enforce explicit decoupling more than others). 11 | 12 | ## Implicit 13 | 14 | In Clean Architecture, it is important to also draw implicit bounded contexts to separate areas of the system that change for different reasons. 15 | 16 | Implicit bounded contexts will not encode separation in anything other than naming / namespacing in terms of grouping functionality. 17 | The benefit here is that the separation is cheap to create and destroy. 18 | 19 | Carefully thinking about the fan-out of UseCases is important. An important consideration is when fan-out crosses a bounded context 20 | e.g. A UseCase related "Financial Reporting" dependending on a gateway in "Authentication". 21 | 22 | It may make sense to have a Financial Reporting user Gateway that depends on a UseCase in Authentication and, database tables specific to the Financial Reporting. 23 | 24 | Database Structures *are* Global variables. So it's important to think about which bounded context "owns" or should encapsulate those databases/tables/columns. 25 | 26 | Similarly it might be important to consider a certain subset of Domain object(s) implicitly bounded context private. 27 | Such that, those Domain objects may only be manipulated (by another bounded context) through the UseCase boundary of that bounded context. 28 | -------------------------------------------------------------------------------- /ruby/examples/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'active_support/core_ext/hash/reverse_merge' 2 | require 'active_model' 3 | require 'action_view' 4 | require 'ostruct' 5 | 6 | class ApplicationController 7 | class Params < OpenStruct 8 | def require(name) 9 | to_h 10 | end 11 | end 12 | 13 | def params=(params) 14 | @params = Params.new(params) 15 | end 16 | 17 | def params 18 | @params 19 | end 20 | 21 | def user_instance_var 22 | @user 23 | end 24 | end 25 | 26 | class View 27 | include ActionView::Helpers::FormHelper 28 | 29 | attr_accessor :output_buffer 30 | 31 | def polymorphic_path(_, _) 32 | '/dummy/url' 33 | end 34 | 35 | def protect_against_forgery? 36 | true 37 | end 38 | 39 | def form_authenticity_token(token = nil) 40 | 'a token' 41 | end 42 | alias_method :request_forgery_protection_token, :form_authenticity_token 43 | end 44 | 45 | module Spree 46 | class Order 47 | def self.find(id) 48 | new 49 | end 50 | 51 | def discount 52 | 10 53 | end 54 | 55 | def update!(attrs) 56 | true 57 | end 58 | end 59 | 60 | class User 61 | include ActiveModel::Model 62 | 63 | def self.create(attrs) 64 | new(attrs).tap(&:valid?) 65 | end 66 | 67 | attr_reader :email 68 | validates :email, presence: true 69 | 70 | def initialize(email:, name:, password:) 71 | @email = email 72 | @name = name 73 | @password = password 74 | end 75 | end 76 | end 77 | 78 | class FakeGateway 79 | def create_user(attrs) 80 | Spree::User.new(attrs).tap(&:valid?).errors 81 | end 82 | end 83 | 84 | require_relative '../lib/orders/domain/discountable_order' 85 | require_relative '../lib/orders/gateway/order' 86 | require_relative '../lib/orders/use_case/apply_order_discount' 87 | 88 | require_relative '../lib/users/gateway/user' 89 | require_relative '../lib/users/use_case/register_user' 90 | 91 | require_relative '../app/controllers/user_registration_controller' 92 | -------------------------------------------------------------------------------- /kotlin/UseCases.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clean Architecture: Kotlin Use Cases 3 | --- 4 | 5 | # Use Cases 6 | 7 | Use Cases can be both asynchronous and synchronous. 8 | 9 | The primary difference is that a synchronous Use Case will return it's result, and an asynchronous Use Case will call a callback with it's result. 10 | 11 | It is possible to generalise the boundary interfaces of these two types of Use Cases. 12 | 13 | ## Asynchronous Example 14 | 15 | Asynchronous use cases provide greater control over rendering to the UI. However they add complexities to testing. 16 | 17 | An [example generalisation can be found here](https://github.com/madetech/dojos/blob/master/mld-klean-architecture/src/main/kotlin/com/madetech/clean/usecase/AsynchronousUseCase.kt), 18 | with [a derivative here](https://github.com/madetech/dojos/blob/master/mld-klean-architecture/src/main/kotlin/io/continuousfeedback/core/usecase/CreateTeamMember.kt) for a specific use case. 19 | 20 | ```kotlin 21 | package com.acmeindustries.widget.usecase 22 | 23 | interface ViewWidgets { 24 | fun execute(request: Request, presenter: Presenter) 25 | 26 | data class Request(...) 27 | interface Presenter { 28 | fun onSuccess() 29 | fun onError() 30 | } 31 | } 32 | ``` 33 | 34 | 35 | ## Synchronous Example 36 | 37 | Synchronous Use Cases provide a simpler interface for testing, but can make representing failure paths and control over the UI harder to maintain. 38 | 39 | ```kotlin 40 | package com.acmeindustries.widget.usecase 41 | 42 | interface ViewWidgetPerFooBarReport { 43 | fun execute(request: Request): Response 44 | 45 | data class Request(public val fromDate: String, public val endDate: String) 46 | data class Response(...) 47 | } 48 | ``` 49 | 50 | ```kotlin 51 | package com.acmeindustries.widget 52 | 53 | import com.acmeindustries.widget.usecase.ViewWidgetPerFooBarReport 54 | import com.acmeindustries.widget.usecase.ViewWidgetPerFooBarReport.* 55 | import com.acmeindustries.widget.domain.Widget 56 | 57 | class WidgetPerFooBarReport(val widgetGateway: WidgetGateway) : ViewWidgetPerFooBarReport { 58 | fun execute(request: Request): Response { 59 | val widgets = widgetGateway.all() 60 | //secret sauce here 61 | return Response(...) //return response populated with data 62 | } 63 | } 64 | 65 | interface WidgetGateway { 66 | fun all(): List 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /learn/basics/fake-gateways.md: -------------------------------------------------------------------------------- 1 | # Fake Gateways 2 | 3 | When building out Acceptance Tests, it is useful to use Fake [test doubles](https://learn.madetech.com/core-skills/tdd/test-doubles.html) to stand in for real gateways. 4 | 5 | This gives you the ability to explore the domain, and the associated business rules with the customer while building real code. 6 | 7 | Once you understand the domain, you can then make an informed decision about technology choices for persistence. 8 | 9 | ## Simplistic Gateway 10 | 11 | Using an array as a backing store, a lot of early gateways might follow this pattern. 12 | 13 | ```ruby 14 | class InMemoryOrder 15 | def initialize 16 | @orders = [] 17 | end 18 | 19 | def find_by(id) 20 | @orders[id] 21 | end 22 | 23 | def all 24 | @orders 25 | end 26 | 27 | def save(order) 28 | @orders << order 29 | @orders.length - 1 30 | end 31 | end 32 | ``` 33 | 34 | ## As part of an acceptance test 35 | 36 | ```ruby 37 | describe 'orders' do 38 | let(:order_gateway) { InMemoryOrder.new } 39 | let(:view_order) { Customer::UseCase::ViewOrder.new(order_gateway: order_gateway) } 40 | let(:place_order) { Customer::UseCase::PlaceOrder.new(order_gateway: order_gateway) } 41 | 42 | context 'given an order has been placed' do 43 | let!(:place_order_response) do 44 | place_order.execute( 45 | customer_id: 3, 46 | shipping_address_id: 1, 47 | billing_address_id: 2, 48 | items: [ 49 | {sku: '19283', quantity: 2} 50 | ] 51 | ) 52 | end 53 | 54 | it 'has placed the order that is viewable' do 55 | response = view_order.execute(order_id: place_order_response[:order_id]) 56 | 57 | expect(response[:items]).to( 58 | eq( 59 | [ {sku: '19283', quantity: 2, price: { amount: '10.00', currency: 'GBP' }} ] 60 | ) 61 | ) 62 | expect(response[:total]).to eq({amount: '10.00', currency: 'GBP'}) 63 | expect(response[:shipping_address_id]).to eq(1) 64 | expect(response[:billing_address_id]).to eq(2) 65 | expect(response[:customer_id]).to eq(3) 66 | end 67 | end 68 | end 69 | ``` 70 | 71 | ## An outer loop, made simple by Fakes 72 | 73 | Acceptance Test Driven Development creates an outer loop around your TDD discipline. 74 | 75 | 1. Write a failing acceptance test 76 | 2. Write the next simplest failing unit test 77 | 3. Write the simplest production code to make the unit test pass 78 | 4. Refactor 79 | 5. Does the acceptance test pass? If *yes* goto 1, *else* goto 2 80 | 81 | Using a Fake test double to stand in for your persistence layer, enables you to exploring both the _domain_ without exploring the _persistence layer_ at the same time. 82 | 83 | -------------------------------------------------------------------------------- /learn/practicality.md: -------------------------------------------------------------------------------- 1 | # Practicality 2 | 3 | The system structure of Made Tech Flavour Clean Architecture is optimised for the set of systems that we would commonly build. 4 | 5 | It has been used successfully to build systems of the following styles: 6 | 7 | - HTTP APIs 8 | - Event-driven GUIs 9 | - Web applications with server-side rendering 10 | - HTTP Middleware (integrating multiple APIs) 11 | - Event-driven systems (using Message Queues) 12 | 13 | Some (limited) exploration has been made using it for: 14 | 15 | - Games programming 16 | - Embedded programming 17 | 18 | ## Rule: one use case per file 19 | 20 | This aspect of the style can cause issues when building reusable libraries. 21 | 22 | Typically you will see this rule being ignored, or a facade pattern employed to optimise the API for ease-of-use. 23 | 24 | For workloads that already have a well-known architecture e.g. a Compiler, it may be desirable to employ that architecture instead. 25 | 26 | ## Rule: use object composition over class inheritance 27 | 28 | One way to provide plugin points is to allow the delivery mechanism to inherit from the high-level policy. 29 | 30 | Imagine you needed to switch on a light, you could use the template method pattern 31 | 32 | Consider the following pseudocode: 33 | 34 | ```ruby 35 | abstract class LightFlasher 36 | 37 | def flash_lights(rate:) 38 | ... 39 | end 40 | 41 | abstract def light_on; 42 | abstract def light_off; 43 | end 44 | ``` 45 | 46 | The real light flasher system could then extend this to create the concrete light flasher. This can be a simple alternative to composition (typically a gateway). 47 | 48 | ## In Haskell 49 | 50 | In Haskell, the most flexible way to implement Clean Architecture is to define a Free Monad with the impure operations that your business rules need to operate on. 51 | 52 | In production you will use a Free Monad interpreter that connects to the real impure operations. 53 | 54 | In unit tests, you will use an interpreter that is actually pure (a test double!), which enables you to test code that encodes impure operations. 55 | 56 | Consider the following pseudocode: 57 | 58 | ```haskell 59 | createOrUpdate personExists createPerson person = if !personExists(person) then createPerson(person) && true else false 60 | ``` 61 | 62 | Since personExists must perform IO (to fetch from a database), and we either perform some IO or not. This function _must_ be impure. 63 | This makes it difficult to test. 64 | 65 | Free Monads provide a way to represent this impure operation as pure data. 66 | 67 | ## Conclusion 68 | 69 | This is why Robert C. Martin intentionally speaks about Clean Architecture in the abstract. 70 | 71 | The _general principle_ of Clean Architecture is to have high-level policy (i.e. Business Rules) not depend on low-level details (e.g. how to speak to PostgreSQL). 72 | 73 | If you achieve this goal through careful use of language features, and your system is working and easy to maintain you have achieved a "Clean Architecture". 74 | 75 | What we do in Made Tech Flavoured Clean Architecture is blend a mix of Domain-Driven-Design, optimise for ATDD, and ease of project navigation given a large number of use cases. 76 | 77 | -------------------------------------------------------------------------------- /use_case.md: -------------------------------------------------------------------------------- 1 | # Use Case 2 | 3 | The purpose of a use case is to serve a user's use case of the system. For example, "turn light on" or "send email to tenant". 4 | 5 | In code, the entry point of a Use Case is a class that has one public method. 6 | 7 | ```ruby 8 | class TurnLightOn 9 | def initialize(light_gateway:) 10 | @light_gateway = light_gateway 11 | end 12 | 13 | def execute(light_id:) 14 | @light_gateway.turn_on(light_id) 15 | {} 16 | end 17 | end 18 | ``` 19 | 20 | This is a simple example, but it only considers the happy path. 21 | 22 | Validation should also be handled by the Use Case too: 23 | 24 | ```ruby 25 | class TurnLightOn 26 | def initialize(light_gateway:) 27 | @light_gateway = light_gateway 28 | end 29 | 30 | def execute(light_id:) 31 | light = @light_gateway.find(light_id) 32 | 33 | return light_not_found if light.nil? 34 | 35 | light.turn_on 36 | 37 | { 38 | success: true, 39 | errors: [] 40 | } 41 | end 42 | 43 | private 44 | 45 | def light_not_found 46 | { 47 | success: false, 48 | errors: [:light_not_found] 49 | } 50 | end 51 | end 52 | ``` 53 | 54 | As you can imagine, depending on the system there may be more complexity needed to service the TurnLightOn use case. 55 | 56 | Use Cases can also use the presenter pattern: 57 | 58 | ```ruby 59 | class TurnLightOn 60 | def initialize(light_gateway:) 61 | @light_gateway = light_gateway 62 | end 63 | 64 | def execute(light_id:, presenter:) 65 | @presenter = presenter 66 | light = @light_gateway.find(light_id) 67 | 68 | light_not_found and return if light.nil? 69 | 70 | turn_light_on(light) 71 | nil 72 | end 73 | 74 | private 75 | 76 | def turn_light_on(light) 77 | light.turn_on 78 | @presenter.success 79 | end 80 | 81 | def light_not_found 82 | @presenter.failure([:light_not_found]) 83 | end 84 | end 85 | ``` 86 | 87 | In this example, the Use Case is not aware of the implementation details of the lighting system, nor how the user is accessing the use case or seeing the errors presented to them. That is handled by the Gateway and the Delivery Mechanism respectively. 88 | 89 | It's not hard to imagine this being called by a button with a red error light, nor is it hard to imagine it used by an iOS application with TouchID activation. 90 | 91 | ## Properties of Use Cases 92 | 93 | * Each use case should be Framework and Database agnostic. 94 | * Use Cases define an interface (implicit in Ruby) that must be fulfilled by a Gateway 95 | * Use Cases expose a request, response interface which are defined as simple data structures (Hashes or Structs) 96 | * In the Presenter pattern the Responses should always be simple data structures. 97 | 98 | ## Alternative names 99 | 100 | * In Ivar Jacobson's BCE architecture these are the "Controls". 101 | * Martin Fowler has a concept called "Transaction Scripts". 102 | * In Uncle Bob's terminology these are "Interactors". 103 | * "Operations" 104 | * "Commands" 105 | 106 | In Made Tech Flavour Clean Architecture we stick to the name "UseCase" 107 | -------------------------------------------------------------------------------- /learn/basics/constructors-for-collaborators.md: -------------------------------------------------------------------------------- 1 | # Constructors for collaborators 2 | 3 | Consider a use case class, such as the following: 4 | 5 | ```ruby 6 | class CreateOrder 7 | def initialize(...) 8 | end 9 | 10 | def execute(...) 11 | end 12 | end 13 | ``` 14 | 15 | What should be a parameter for the initializer (the constructor) and what should be a parameter for the execute method? 16 | 17 | ## A non-reentrant example 18 | 19 | A fairly typical design is to pass everything to the constructor. 20 | 21 | Lets also assume a Clean Architecture design, and for familiarity that we are using Rails, and see what it looks like in a controller... 22 | 23 | ```ruby 24 | class OrderController < ApplicationController 25 | def create 26 | order_gateway = ActiveRecordOrderGateway.new(Order) 27 | CreateOrder.new(order_gateway, create_order_request).execute 28 | end 29 | end 30 | ``` 31 | 32 | This has one particular downside in that you must have both a reference to the gateway and the request at the callsite to `.execute`. 33 | 34 | To understand why this is an issue, lets look at a reentrant example. 35 | 36 | ## A reentrant example 37 | 38 | We pass the request at the `.execute` callsite, and pass the gateway to the constructor. 39 | 40 | Consider the following controller... 41 | 42 | ```ruby 43 | class OrderController < ApplicationController 44 | def create 45 | @create_order.execute(create_order_request) 46 | end 47 | end 48 | ``` 49 | 50 | What this has enabled us to do is separate the construction of our objects, from the usage of the object. 51 | 52 | To say this another way, we can write controllers that are entirely unaware of the `order_gateway` dependency. 53 | 54 | This is the interface segregation principle at work. 55 | 56 | ## But... 57 | 58 | I could just make `order_gateway` an instance variable? Like this... 59 | 60 | ```ruby 61 | class OrderController < ApplicationController 62 | def create 63 | CreateOrder.new(@order_gateway, create_order_request).execute 64 | end 65 | end 66 | ``` 67 | 68 | Yes you could. And this is certainly better, in terms of code reuse. 69 | 70 | There are two downsides 71 | 72 | - There is a source code dependency on the `CreateOrder` class constant. (Dependency Inversion Principle violation) 73 | - Knowledge of an unnecessary dependency `@order_gateway`. (Interface Segregation Principle violation) 74 | 75 | Both of these facts make it harder to unit test this `OrderController`. 76 | 77 | ## Sinatra 78 | 79 | Lets assume we're using Sinatra for a moment and lets consider the following code... 80 | 81 | ```ruby 82 | post '/add-user' do 83 | controller = Controllers::AddUser.new( 84 | add_user: @dependency_factory.get_use_case(:add_user) 85 | ) 86 | controller.execute(params, request_hash, response) 87 | end 88 | ``` 89 | 90 | We have created an MVC structure without Rails. We have a Controller, bound to a route `/add-user`. 91 | 92 | This controller has explicit dependencies, passed in via the constructor parameters. 93 | 94 | All request parameters are passed in via parameters to the `.execute` method. 95 | 96 | This controller is now isolated from Sinatra - we can unit test it without the framework, and without the business rules. 97 | 98 | ## Conclusion 99 | 100 | By making constructors for collaborators only (Dependencies), we are able to seperate construction from the usage of objects. 101 | 102 | This fact makes it easier to decouple aspects of your system for easier testing, and have the flexibility to compose them. 103 | 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Architecture 2 | 3 | How to begin with "Made Tech Flavoured Clean Architecture". 4 | 5 | This style of architecture has had many names over the years including "Hexagonal", "Ports & Adapters" and "Boundary-Control-Entity". 6 | 7 | # Getting started 8 | 9 | ## Architectural Concepts 10 | 11 | * [Use Case](use_case.md) 12 | * [Domain](domain.md) 13 | * [Gateway](gateway.md) 14 | * [Bounded Contexts](bounded_contexts.md) 15 | 16 | ## Learn by example (Ruby) 17 | 18 | The best way to learn Clean Architecture is through deliberate practice. 19 | 20 | *(Work-in-progress)* 21 | 22 | ### Basics 23 | 24 | * [The Mindset](learn/the-mindset.md) 25 | * [Start with Acceptance Testing](learn/basics/start-with-acceptance.md) 26 | * [Writing Fake Gateways](learn/basics/fake-gateways.md) 27 | * [Use Cases organise your code](learn/basics/use-cases-organise.md) 28 | * [Constructors are for collaborators](learn/basics/constructors-for-collaborators.md) 29 | * [Don't leak your internals!](learn/basics/do-not-leak-your-internals.md) 30 | * [TDD everything](learn/basics/tdd-everything.md) 31 | * [Build in a reliable dependency upgrade path](learn/basics/reliable-dependencies.md) 32 | * [Your first Real Gateway](learn/basics/gateway-101.md) 33 | * [Your first Delivery Mechanism](learn/basics/delivery-mechanism-101.md) 34 | 35 | ### Intermediate 36 | 37 | * [Presenters are more flexible](learn/intermediate/flexible-presenters.md) 38 | * [Keep your wiring DRY](learn/intermediate/keep-your-wiring-DRY.md) 39 | * [Extend Use Case behaviour with Domain objects](learn/intermediate/extend-with-domain.md) 40 | * [Extracting a Use Case from a Use Case](learn/intermediate/extract-use-case-from-another.md) 41 | * [Authentication](learn/intermediate/authentication.md) 42 | * [Authorisation](learn/intermediate/authorisation.md) 43 | 44 | ### Advanced 45 | 46 | * [Consider the Actors](learn/advanced/consider-the-actors.md) 47 | * [Substitutable Use Cases](learn/advanced/substitutable-use-cases.md) 48 | * [Feature Toggles](learn/advanced/feature-toggles.md) 49 | * [Keep your Domain object construction DRY](learn/advanced/keep-your-domain-object-construction-dry.md) 50 | 51 | ## Examples in Languages 52 | 53 | * [Ruby](ruby/README.md) 54 | * [Kotlin](kotlin/README.md) 55 | * Go 56 | * Clojure 57 | * JS 58 | 59 | # Further Reading 60 | 61 | [Clean Architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html) by Robert C. Martin is extremely similar in nature to 62 | 63 | * [BCE](https://www.amazon.com/Object-Oriented-Software-Engineering-Approach/dp/0201544350) by Ivar Jacobson and, 64 | * [Hexagonal Architecture](http://alistair.cockburn.us/Hexagonal+architecture) (also known as **Ports & Adapters**) by Alistair Cockburn. 65 | 66 | The Made Tech flavour is slightly different still to exactly what is described in [Robert C. Martin's book about Clean Architecture](https://www.amazon.co.uk/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164), the choice to rename certain basic concept is deliberate to aid: 67 | 68 | - Learning as a Junior 69 | - Relating Interactors (Robert's name for UseCase objects) to Use Case Analysis sessions 70 | - Retaining an eye on Domain-Driven-Design i.e. What are Domain objects? 71 | - Avoiding overloading terminology e.g. Entity (Robert's name for Domain Objects) with EntityFramework Entities 72 | 73 | Made Tech flavour Clean Architecture is more [prescriptive than any of these other examples](learn/practicality.md) 74 | 75 | ## Reference 76 | 77 | * [Clean Coders videos](https://cleancoders.com/videos/clean-code) 78 | * [Clean Architecture Book](https://www.amazon.co.uk/Clean-Architecture-Craftsmans-Software-Structure/dp/0134494164/) 79 | 80 | -------------------------------------------------------------------------------- /learn/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Learning Clean Architecture 3 | --- 4 | 5 | 6 | # Learning Clean Architecture 7 | 8 | ## Values 9 | 10 | Clean Architecture works best when all programmers share the same mindset, or at the very least understand and apply its mindset. 11 | 12 | *We are uncovering better ways of writing code and architecting software by doing it and helping others do it. Through this work we have come to value:* 13 | 14 | **Expressing the domain simply** over expressing the domain via tools and frameworks 15 | 16 | **Executable documentation** over non-executable documentation 17 | 18 | **Customer usage, deferring technology choices** over fitting customer usage into technology choices 19 | 20 | **Delivering value with low cost of change** over delivering hard to change value sooner 21 | 22 | *That is, while there is value in the items on 23 | the right, we value the items on the left more.* 24 | 25 | ## Principles 26 | 27 | * Code rots and becomes a big ball of mud when programmers fear changing code 28 | * Applying a sound strategy for preventing the introduction of defects, such as TDD, unpins eliminating fear. [(semantic stability)](https://www.madetech.com/blog/semantically-stable-test-suites) 29 | * Refactoring can occur at any time when there is no fear 30 | * When the team understanding of the domain improves, keeping the in-code model of the domain up to date is important. 31 | * The SOLID and package principles provide a guide to aid good software design 32 | 33 | ## Learning 34 | 35 | A typical number used to determine how much effort is required to become an expert is 10,000 hours of practice. 36 | 37 | It cannot be any practice, however, i.e. you cannot swing a golf club for 10,000 hours and become as good as Tiger Woods. 38 | 39 | "Practice" must be: 40 | 41 | * Deliberate and goal directed 42 | * Include the opportunity for: 43 | * Self-reflection 44 | * Feedback 45 | 46 | ## Other guides 47 | 48 | * [ATDD](ATDD.md) 49 | 50 | ## Core Skills 51 | 52 | * Able to describe 53 | - OO language features 54 | - the responsibility of each organising component of a clean architecture 55 | - SOLID principles 56 | 57 | * Able to identify 58 | - OO language features 59 | - the core organising components of a clean architecture in a code base 60 | - concrete examples where the forces of the SOLID and Package principles are at play 61 | 62 | * Able to implement and use in code 63 | - OO language features 64 | - all the core organising components of a clean architecture 65 | - the SOLID principles as a tool to help guide the shape of your architecture 66 | - the Package principles as a tool to help guide the organisation of your packages 67 | 68 | # Clean Architecture skill-set 69 | 70 | * Able to perform analysis of potential use case(s) 71 | * Determine an order to work through use case(s) that will test the most assumptions 72 | * Determine input data structure 73 | * Determine output data structure 74 | * Determine which Domain object(s) are potentially required 75 | * Determine potential interface of any collaborator(s) 76 | * Able to perform analysis of appropriate use of asynchronous vs synchronous use cases 77 | * Able to use type systems to aid construction, refactoring and robustness (rather than primarily a hindrance) 78 | * Able to make use of IDE refactoring tools to aid refactoring and construction 79 | * Able to apply TDD to provide the basis of a good testing strategy 80 | * Able to apply ATDD to provide extra robustness and a customer-goal-oriented testing approach 81 | * Able to recognise recurring themes of the development process, and the common challenges faced in each 82 | * Null-step (wiring and boilerplate) 83 | * Degenerate cases 84 | * Passing the first acceptance test 85 | * Creating your second use case 86 | * Creating generalisations 87 | * ... 88 | * Able to support and mentor others in recurring themes of the development process 89 | * Be able to (self-)organise/communicate with other teams also writing code in the same parts of the system 90 | 91 | # Object-oriented principles 92 | 93 | Below is a list of OO tools & skills that are non-specific to Clean Architecture. 94 | 95 | It is ideal if you have knowledge of how each of these works in your language of choice (assuming it supports the listed language feature). 96 | 97 | * OO 98 | * Polymorphism 99 | * Encapsulation 100 | * Composition 101 | * Abstract class 102 | * Inheritance 103 | * Reference and value types 104 | * Static 105 | * Overriding 106 | * Exceptions 107 | 108 | * Type-safe OO: 109 | * Interface 110 | * Concrete class 111 | * Generics 112 | 113 | ### Principles of Object-oriented programming 114 | 115 | These are widely accepted as the forces at play when developing OO software. While there is opposition to that, we 116 | assume when building Cleanly architected systems, that they hold true. 117 | 118 | * SOLID principles 119 | * Single responsibility principle 120 | * Open closed principle 121 | * Liskov substitution principle 122 | * Interface segregation principle 123 | * Dependency inversion principle 124 | 125 | * Package principles 126 | * Cohesion 127 | * Reuse-release equivalence principle 128 | * Common-reuse principle 129 | * Common-closure principle 130 | * Coupling 131 | * Acyclic dependencies principle 132 | * Stable-dependencies principle 133 | * Stable-abstractions principle 134 | -------------------------------------------------------------------------------- /learn/basics/start-with-acceptance.md: -------------------------------------------------------------------------------- 1 | # Start with Acceptance Testing 2 | 3 | 4 | Acceptance testing is where you should start before writing anything. 5 | Similarly, if in doubt, always check your acceptance tests and go from there. 6 | 7 | Here, we're going to be describing something that looks like the A in [ATDD](https://en.wikipedia.org/wiki/Acceptance_test%E2%80%93driven_development), with [BDD](https://en.wikipedia.org/wiki/Behavior-driven_development) in the mix too. 8 | 9 | ## What is an acceptance test? 10 | 11 | It is a high-level set of tests, written from the perspective of the user, describing steps through the system, and expectations along the way. 12 | 13 | In BDD the behaviour of the system might be defined light so: 14 | 15 | ```cucumber 16 | Given the light is off 17 | When I turn the light on 18 | Then the light is on 19 | ``` 20 | 21 | Here is the same Cucumber script written as RSpec Ruby: 22 | 23 | ```ruby 24 | describe 'lighting' do 25 | let(:system) { LightingSystem.new } 26 | let(:create_light_use_case) { system.get_use_case(:create_light) } 27 | let(:turn_light_on_use_case) { system.get_use_case(:turn_light_on) } 28 | let(:view_light_status_use_case) { system.get_use_case(:view_light_status) } 29 | 30 | let(:light_id) do 31 | response = create_light_use_case.execute 32 | response[:id] 33 | end 34 | 35 | let(:view_light_status_response) do 36 | view_light_status_use_case.execute(light_id: light_id) 37 | end 38 | 39 | context 'given the light is off' do 40 | it 'is off' do 41 | expect(view_light_status_response[:on]).to be(false) 42 | end 43 | 44 | context 'when I turn the light on' do 45 | before { turn_light_on_use_case.execute(light_id: light_id) } 46 | 47 | it 'is on' do 48 | expect(view_light_status_response[:on]).to be(true) 49 | end 50 | end 51 | end 52 | end 53 | ``` 54 | 55 | ## Write acceptance tests first 56 | 57 | The first step before writing any code is to write a failing acceptance test. 58 | 59 | We want to describe what the customer needs before we begin work. 60 | 61 | ### Why? 62 | 63 | We do not want to 64 | * get distracted, 65 | * lose focus, 66 | * write more code than necessary, or 67 | * run into situations where the moving parts do not work together 68 | 69 | **More than anything, we want to understand what we're trying to achieve.** 70 | 71 | ### What should an acceptance test suite test? 72 | 73 | Tests have three components: **Arrange**-**Act**-**Assert**, lets look at what should be exercised in each step. 74 | 75 | Let's examine each in reverse order 76 | 77 | #### Assert 78 | 79 | **Ideally:** Execute a use case and ensure the result it responds with is expected. 80 | 81 | ```ruby 82 | it 'is off' do 83 | view_light_status_response = view_light_status.execute( 84 | light_id: light_id 85 | ) 86 | expect(view_light_status_response[:on]).to be(false) 87 | end 88 | 89 | ``` 90 | 91 | However, if your application is not fully built yet a small shortcut might be to go to a gateway directly to achieve your assertion. This allows you to take small slices through your work. 92 | 93 | Tightly coupling to gateways is not ideal: 94 | 95 | * Makes it harder to refactor the interface between use cases and gateways 96 | * Causes acceptance tests to be privvy to the interals of your application i.e. Domain objects 97 | * Your acceptance tests will need to be changed (code churn) more often due to this 98 | 99 | ##### From the trenches 100 | 101 | More than one use case may be aware of a particular Domain object. In situations where this is more than a couple, it is common to extract factories or builders to create Domain objects for you (reused in both test and production code). 102 | 103 | Changing/refactoring the API of a Domain object may require no changes to any acceptance tests if your acceptance specs never see them. Indeed, it is often possible to change one aspect of unit test code to achieve the same end if there are appropriate abstractions in place. 104 | 105 | 106 | #### Act 107 | 108 | The code you exercise in the Act step of an acceptance test is always going to be a use case's boundary. 109 | 110 | ```ruby 111 | context 'when I turn the light on' do 112 | before { turn_light_on_use_case.execute(light_id: light_id) } 113 | end 114 | ``` 115 | 116 | 117 | ##### From the trenches 118 | Beware of specifying the needs of your customer in API tests (e.g. Rails feature-spec). 119 | 120 | Let me explain how the Single Responsibility Principle manifests itself in Acceptance Testing. 121 | 122 | Tightly coupling descriptions of what your customer needs to your HTTP-stack can cause code churn on your acceptance tests for technical reasons, not domain reasons. For example, a cookie might need to be set, or a new version of HTTP/Ajax/JS requires some _sort of widget to be reticulated_. 123 | 124 | It is hard to concentrate on two problems at once. If you are changing a test suite because some _spline needs reticulating in your HTTP SPDY Headers_, are you going to be focussing on the fine points of your customer's domain? 125 | 126 | Could you potentially introduce a hole in your test suite inadvertently? In any moderate-to-complex system that risk is higher than you probably expect. 127 | 128 | Acceptance Tests specify the needs of the customer, nothing more or less. 129 | 130 | Separate the concerns both in Production Code and, most importantly, your Test Code. 131 | 132 | #### Arrange 133 | 134 | Setting up your Acceptance Tests is one of the most difficult of the testing arts to become adept in. 135 | 136 | In the simplest case, your "Arrange" step is merely a case of calling one or more use cases to get the system to the state you need. This is an example of the ideal world, this is what system designers should aim for. 137 | 138 | That said, while this is the ideal it may not be practical or possible. 139 | 140 | Aim to have your test setup code mimic how you'd expect your application to be used by it's delivery mechanism. 141 | 142 | ## Should we use Code or a Domain Specific Language? 143 | 144 | Gherkin (Cucumber/SpecFlow) and Fitnesse are common DSL choices for writing executable acceptance tests. 145 | 146 | If you are involving your (non-programmer) stakeholders in creation and verification of acceptance tests, you should probably use a DSL. If you are not doing this, use code, but try to still use human-readable language. 147 | 148 | ```Gherkin 149 | Feature: An customer places an order 150 | 151 | Scenario: An existing customer places an order 152 | Given an existing customer 153 | And a valid UK billing address 154 | And a valid UK shipping address 155 | And wants to buy 1x sku 19283 156 | When the order is placed 157 | Then the order is viewable 158 | And there is one line item 159 | And there is valid UK shipping address 160 | And there is a valid UK billing address 161 | And there is a line item for 1x sku 19283 for 10.00 GBP 162 | And the order total is 10.00 GBP 163 | ``` 164 | --------------------------------------------------------------------------------