Slide 1

Slide 1 text

A Case for Use Cases

Slide 2

Slide 2 text

Use Cases VS The State (of Rails applications)

Slide 3

Slide 3 text

Shevaun Coker @shevauncoker Prosecutor

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Shevaun Coker Court Reporter

Slide 7

Slide 7 text

Use Case Defendant

Slide 8

Slide 8 text

Charges

Slide 9

Slide 9 text

Reducing coupling between the Rails framework and business domain Charges

Slide 10

Slide 10 text

Increasing visibility of features within the codebase Charges

Slide 11

Slide 11 text

Encapsulating complex business logic Charges

Slide 12

Slide 12 text

The following presentation contains explicit code scenes. Viewer discretion is advised. Warning!

Slide 13

Slide 13 text

Opening Statement

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Sole Requirement: A buyer can purchase a photo

Slide 17

Slide 17 text

Photo Buyer Purchase

Slide 18

Slide 18 text

$ rails g model Buyer

Slide 19

Slide 19 text

$ rails g model Buyer class Buyer < ActiveRecord::Base end

Slide 20

Slide 20 text

$ rails g model Photo

Slide 21

Slide 21 text

class Photo < ActiveRecord::Base end $ rails g model Photo

Slide 22

Slide 22 text

$ rails g model Purchase

Slide 23

Slide 23 text

$ rails g model Purchase class Purchase < ActiveRecord::Base belongs_to :photo belongs_to :buyer end

Slide 24

Slide 24 text

$ rails g controller Purchases

Slide 25

Slide 25 text

$ rails g controller Purchases class PurchasesController < ApplicationController def create Purchase.create!(buyer: buyer, photo: photo) redirect_to purchase_completed_path end end

Slide 26

Slide 26 text

$ rails g controller Purchases class PurchasesController < ApplicationController def create Purchase.create!(buyer: buyer, photo: photo) redirect_to purchase_completed_path end end

Slide 27

Slide 27 text

class PurchasesController < ApplicationController def create Purchase.create!(buyer, photo) redirect_to purchase_completed_path end end $ rails g controller Purchases

Slide 28

Slide 28 text

class PurchasesController < ApplicationController def create Purchase.create!(buyer, photo) redirect_to purchase_completed_path end end $ rails g controller Purchases

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

SOME TIME LATER…

Slide 31

Slide 31 text

Sole Requirement: A buyer can purchase a photo s A buyer receives a purchase invoice Record the sales count of each photo A buyer can review a purchased photo Updated

Slide 32

Slide 32 text

class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMail.invoice.deliver(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end

Slide 33

Slide 33 text

class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end

Slide 34

Slide 34 text

class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end

Slide 35

Slide 35 text

class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 39

Slide 39 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 40

Slide 40 text

class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end

Slide 41

Slide 41 text

class PurchasesController def create purchase = Purchase.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) redirect_to purchase_completed_path end end

Slide 42

Slide 42 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 43

Slide 43 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 44

Slide 44 text

class PurchasesController def create Purchase.create_for_buyer!(buyer, photo) redirect_to purchase_completed_path end end

Slide 45

Slide 45 text

class PurchasesController def create Purchase.create_for_buyer!(buyer, photo) redirect_to purchase_completed_path end end

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

http://wall.alphacoders.com/big.php?i=117571

Slide 48

Slide 48 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 49

Slide 49 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 50

Slide 50 text

class Purchase < ActiveRecord::Base; end def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 51

Slide 51 text

class Purchase < ActiveRecord::Base; end def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end > Purchase.instance_methods.size - Object.instance_methods.size => 214

Slide 52

Slide 52 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 53

Slide 53 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 54

Slide 54 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 55

Slide 55 text

class Purchase < ActiveRecord::Base def self.create_for_buyer(buyer, photo) purchase = self.create!(buyer, photo) BuyerMailer.send_invoice(buyer, purchase) photo.increment_sales_count buyer.enable_review_of(photo) end end

Slide 56

Slide 56 text

Too many responsibilities

Slide 57

Slide 57 text

Harder to change

Slide 58

Slide 58 text

More error & conflict prone

Slide 59

Slide 59 text

Huge class files

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

http://techmaster.vn/2014/07/blogging-rails-02-mvc-trong-rails/

Slide 62

Slide 62 text

Taking the Stand

Slide 63

Slide 63 text

What is a Use Case?

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

A use case is a written description of how users will perform tasks on your website. usability.gov

Slide 66

Slide 66 text

It outlines, from a user's point of view, a system's behaviour as it responds to a request. usability.gov

Slide 67

Slide 67 text

Each use case is represented as a sequence of simple steps, beginning with a user's goal and ending when that goal is fulfilled. usability.gov

Slide 68

Slide 68 text

http://commons.wikimedia.org/wiki/File:CCTS_CI_Use_Case.png

Slide 69

Slide 69 text

Reflect the architecture in the code

Slide 70

Slide 70 text

Reflect the use cases in the code

Slide 71

Slide 71 text

Examining the Witness

Slide 72

Slide 72 text

How did we implement use cases at Envato?

Slide 73

Slide 73 text

“an action a user of a system takes to achieve a goal”

Slide 74

Slide 74 text

* PurchasePhoto * ConfirmEmail * ResetPassword * VerifySignIn

Slide 75

Slide 75 text

$ls app/use_cases purchase_photo.rb confirm_email.rb reset_password.rb verify_sign_in.rb

Slide 76

Slide 76 text

module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap { |use_case| use_case.perform } end end def perform raise NotImplementedError end end

Slide 77

Slide 77 text

module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap { |use_case| use_case.perform } end end def perform raise NotImplementedError end end

Slide 78

Slide 78 text

module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap { |use_case| use_case.perform } end end def perform raise NotImplementedError end end

Slide 79

Slide 79 text

module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap { |use_case| use_case.perform } end end def perform raise NotImplementedError end end

Slide 80

Slide 80 text

module UseCase extend ActiveSupport::Concern module ClassMethods def perform(*args) new(*args).tap { |use_case| use_case.perform } end end def perform raise NotImplementedError end end

Slide 81

Slide 81 text

class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 82

Slide 82 text

class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 83

Slide 83 text

class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 84

Slide 84 text

class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 85

Slide 85 text

class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 86

Slide 86 text

class PurchasePhoto include UseCase def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 87

Slide 87 text

private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice Buyer::InvoiceMailer.enqueue(purchase) end def increment_sales_count Photo::SalesCount.increment(photo) end def allow_buyer_to_review_photo Photo::ReviewPermissions.add_buyer(buyer, photo) end

Slide 88

Slide 88 text

class PurchasePhoto include UseCase include ActiveModel::Validations def success? errors.none? end end

Slide 89

Slide 89 text

class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to purchase_completed_path else redirect_to purchase_failed_path end end end

Slide 90

Slide 90 text

class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to purchase_completed_path else redirect_to purchase_failed_path end end end

Slide 91

Slide 91 text

class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to purchase_completed_path else redirect_to purchase_failed_path end end end

Slide 92

Slide 92 text

class PurchasesController def create use_case = PurchasePhoto.perform if use_case.success? redirect_to purchase_completed_path else redirect_to purchase_failed_path end end end

Slide 93

Slide 93 text

Hearing from the Defence

Slide 94

Slide 94 text

Lessons Learned

Slide 95

Slide 95 text

Command Query Separation Bertrand Meyer, Object Oriented Software Construction Lesson 1

Slide 96

Slide 96 text

Command: has side effects, no return value

Slide 97

Slide 97 text

Query: idempotent, returns a value

Slide 98

Slide 98 text

“We conflate commands and queries at our peril” Sandi Metz, Magic Tricks of Testing 2013

Slide 99

Slide 99 text

A great fit for Integration Tests Lesson 2

Slide 100

Slide 100 text

acceptance integration unit Test Coverage

Slide 101

Slide 101 text

RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo) } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end

Slide 102

Slide 102 text

RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo) } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end

Slide 103

Slide 103 text

RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo) } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end

Slide 104

Slide 104 text

RSpec.describe PurchasePhoto do describe “#perform” do subject(:perform) { PurchasePhoto.perform(buyer, photo) } it “creates a purchase record” do expect { perform }.to change { Purchase.count }.by(1) end it “sends an invoice” do perform expect(Mailer.deliveries.last). to have_attributes(subject: “Purchase Invoice”) end end end

Slide 105

Slide 105 text

Don’t put low level logic in your UseCase Lesson 3

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

No content

Slide 108

Slide 108 text

private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice Buyer::InvoiceMailer.enqueue(purchase) end def increment_sales_count Photo::SalesCount.increment(photo) end def allow_buyer_to_review_photo Photo::ReviewPermissions.add_buyer(buyer, photo) end

Slide 109

Slide 109 text

Don’t make your UseCases generic Lesson 4

Slide 110

Slide 110 text

http://veryfunnypics.eu/2013/07/19/canadian-protest-signs/

Slide 111

Slide 111 text

Presenting the Evidence

Slide 112

Slide 112 text

A Consistent Interface Provides

Slide 113

Slide 113 text

PurchasePhoto.perform(buyer, photo) class PurchasePhoto include UseCase #... end

Slide 114

Slide 114 text

PurchasePhoto.perform(buyer, photo) class PurchasePhoto include UseCase #... end

Slide 115

Slide 115 text

Self Documenting & Readable Code Provides

Slide 116

Slide 116 text

class PurchasePhoto include UseCase def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 117

Slide 117 text

private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice Buyer::InvoiceMailer.enqueue(purchase) end def increment_sales_count Photo::SalesCount.increment(photo) end def allow_buyer_to_review_photo Photo::ReviewPermissions.add_buyer(buyer, photo) end

Slide 118

Slide 118 text

Context & Encapsulation Provides

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

class PurchasePhoto def initialize(buyer, photo) @buyer = buyer @photo = photo end def perform # steps end end

Slide 121

Slide 121 text

Extensibility Provides

Slide 122

Slide 122 text

private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice Buyer::InvoiceMailer.enqueue(purchase) end # ... def send_purchase_confirmation Purchase::ConfirmationMailer.enqueue(purchase) end def allow_buyer_to_review_photo

Slide 123

Slide 123 text

private def create_purchase @purchase = Purchase.create!(buyer, photo) end def send_invoice Buyer::InvoiceMailer.enqueue(purchase) end # ... def send_confirmation_email Purchase::ConfirmationMailer.enqueue(purchase) end def allow_buyer_to_review_photo

Slide 124

Slide 124 text

class PurchasePhoto include UseCase def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo end end

Slide 125

Slide 125 text

class PurchasePhoto include UseCase def perform create_purchase send_invoice increment_sales_count allow_buyer_to_review_photo send_confirmation_email end end

Slide 126

Slide 126 text

Decoupling from your framework Provides

Slide 127

Slide 127 text

No content

Slide 128

Slide 128 text

A codebase that reveals its features Provides

Slide 129

Slide 129 text

$ls app/use_cases purchase_photo.rb confirm_email.rb reset_password.rb verify_sign_in.rb

Slide 130

Slide 130 text

Objection!

Slide 131

Slide 131 text

http://i.imgur.com/aXTps4g.jpg

Slide 132

Slide 132 text

Use Case is a perfectly cromulent name

Slide 133

Slide 133 text

Illustration by Leslie Herman

Slide 134

Slide 134 text

cjohansen/use_case cypriss/mutations stevehodgkiss/interaction collectiveidea/interactor https://github.com/

Slide 135

Slide 135 text

cjohansen/use_case cypriss/mutations stevehodgkiss/interaction collectiveidea/interactor https://github.com/

Slide 136

Slide 136 text

cjohansen/use_case cypriss/mutations stevehodgkiss/interaction collectiveidea/interactor https://github.com/

Slide 137

Slide 137 text

cjohansen/use_case cypriss/mutations stevehodgkiss/interaction collectiveidea/interactor https://github.com/

Slide 138

Slide 138 text

cjohansen/use_case cypriss/mutations stevehodgkiss/interaction collectiveidea/interactor https://github.com/

Slide 139

Slide 139 text

Closing Statement

Slide 140

Slide 140 text

Reduces Coupling Increases feature visibility Encapsulates complex logic

Slide 141

Slide 141 text

Reduces Coupling Increases feature visibility Encapsulates complex logic GUILTY

Slide 142

Slide 142 text

CASE CLOSED

Slide 143

Slide 143 text

webuild.envato.com/blog/a-case-for-use-cases