Slide 1

Slide 1 text

実践! Hanamiで Clean Architecture 2021年6⽉15⽇ 藤澤 亮平 1

Slide 2

Slide 2 text

⾃⼰紹介 藤澤 亮平 - ID - Github︓fujisawaryohei - Twitter︓@potaku_dev - Work at - 株式会社 Fusic (フュージック) 第⼆技術部⾨ 所属 - ソフトウェアエンジニア - Skill - Rails/Vue.js/Nuxt.js/TypeScript/ AWS SAA - SNS - Github︓fujisawaryohei - Twitter︓@potaku_dev - Qiita︓fujisawaryohei 2

Slide 3

Slide 3 text

01 なぜHanamiを学ぶのか

Slide 4

Slide 4 text

クリーンアーキテクチャの 設計思想や変更に強いコード の書き⽅を学ぶことができる

Slide 5

Slide 5 text

02 クリーンアーキテクチャとは

Slide 6

Slide 6 text

プロダクトのコアロジックやFW, DB, UI等の ソフトウェアを構成する様々な要素間の 関⼼を分離することによってソフトウェアを 構成するFW,DB,UI等の変更から製品のコアロ ジックを守ることを⽬的とするアーキテクチャ

Slide 7

Slide 7 text

03 クリーンアーキテクチャの メリット

Slide 8

Slide 8 text

1. FW⾮依存・DB⾮依存・UI ⾮依存な設計を⾏う事ができる

Slide 9

Slide 9 text

2. 独⽴性が⾼まり テスタビリティが向上する

Slide 10

Slide 10 text

04 クリーンアーキテクチャ 概要

Slide 11

Slide 11 text

タイトル

Slide 12

Slide 12 text

DIを⽤いてSOLID原則の1つである 依存関係逆転の原則を満たす事ができる。 これによって依存関係の制御を⾏うことができる。 どうやって依存しないようにするの︖︖

Slide 13

Slide 13 text

Entites

Slide 14

Slide 14 text

タイトル この部分

Slide 15

Slide 15 text

エンティティ層は最重要ビジネスデータと最重要ビジネス ルールを持つクラスが存在するレイヤー Entities層とは

Slide 16

Slide 16 text

最重要ビジネスデータ: システム化する以前から存在するビジネスデータ 最重要ビジネスルール: システム化する以前から存在するビジネスルール 最重要ビジネスルール, 最重要ビジネスデータとは

Slide 17

Slide 17 text

Use Cases

Slide 18

Slide 18 text

タイトル この部分

Slide 19

Slide 19 text

アプリケーション固有のビジネスルール システム化するに辺り⽣じるルールを記述するレイヤー UseCases層とは

Slide 20

Slide 20 text

Interface Adpters

Slide 21

Slide 21 text

タイトル この部分

Slide 22

Slide 22 text

ユースケースやエンティティに最も便利な形式から、 DBやWebのUIのような外部の機能に 最も便利な形式にデータを変換処理する場所 Controllerとは

Slide 23

Slide 23 text

Frameworks&Drivesからのデータを抽象化する Gatewayとは

Slide 24

Slide 24 text

ユースケースから返ってきたデータを適切な形に変 換してViewに渡すロジックが集約する場所 Presenterとは

Slide 25

Slide 25 text

Interface Adapter層を 要約すると・・

Slide 26

Slide 26 text

1. 外側のレイヤーから受け取ったデータを 適切なデータ形式に変換しUseCase層へ渡す処理 2. UseCase層で処理して返ってきたデータを 外側のレイヤーに適切なデータ形式に再度変換して 渡す処理 これらの責務を担うレイヤーとなります。 Interface Adapter層

Slide 27

Slide 27 text

タイトル つまりこれ

Slide 28

Slide 28 text

Framework and Drivers

Slide 29

Slide 29 text

タイトル この部分

Slide 30

Slide 30 text

05 Hanami

Slide 31

Slide 31 text

Clean Architectureの設計思想が反映された Rubyのフレームワーク Hanamiとは

Slide 32

Slide 32 text

Clean Architecure The main purpose of this architecture is to enforce a separation of concerns between the core of our product and the delivery mechanisms. The first is expressed by the set of use cases that our product implements, while the latter are interfaces to make these features available to the outside world. このHanamiのアーキテクチャの目的は、製品のコアロジック の関心とその製品を配信するメカニズムの関心の分離を強化 することです。前者は実装する一連のユースケースによって 表現され、後者はこれらの機能を外部の世界で利用できるよ うにするためのインターフェースです。

Slide 33

Slide 33 text

・hanami-model(モデルで使⽤するパッケージ) ・hanami-controller(コントローラーで使⽤するパッケージ) ・hanami-view(ビューで使⽤するパッケージ) ・hanami-validation(バリデーションで使⽤するパッケージ) ・hanami-util(便利なパッケージが⼀括してパッケージングされている) Hanamiパッケージ

Slide 34

Slide 34 text

Hanamiのアーキテクチャ

Slide 35

Slide 35 text

ディレクトリ構成 ・controller, Viewは apps/webディレクトリ ・Entity, Repository, Interactor, Presenterは lib/project_nameディレクトリ

Slide 36

Slide 36 text

06 Hanamiで実践︕ クリーンアーキテクチャ

Slide 37

Slide 37 text

本の登録フォームから「 タイトル 」「 作者 」「単価」 これらの⼊⼒を⾏い登録するユースケース Web上で登録した本の「 タイトル 」「 作者 」「単価」 に基づいた税込価格の確認ができる 今回のユースケース

Slide 38

Slide 38 text

# Hanamiのプロジェクト作成 ▶ bundle exec hanami new project_name # hanamiのモデル作成 ▶ bundle exec hanami g model model_name # controlleとaction⽣成 ▶ bundle exec hanami g action web controller_name#action_name # マイグレーション (HANAMI_ENV=testを先頭につける事でテスト⽤DBを実⾏できます) ▶ bundle exec hanami hanami db prepare # テストコード実⾏ ▶ bundle exec hanami exec rake Hanami コマンド⼀覧

Slide 39

Slide 39 text

Entity # /lib/bookshelf/entities/book.rb # Table name: books # # id :bigint not null, primary key # author :string not null # title :string not null # created_at :datetime not null # updated_at :datetime not null class Book < Hanami::Entity def total_price self.unit_price * 1.08 end end

Slide 40

Slide 40 text

Repository # /lib/bookshelf/repositories/book_repository.rb class BookRepository < Hanami::Repository # create(data) – Create a record for the given data (or entity) # update(id, data) – Update the record corresponding to the given id by setting the given data (or entity) # delete(id) – Delete the record corresponding to the given id # all - Fetch all the entities from the relation # find - Fetch an entity from the relation by primary key # first - Fetch the first entity from the relation # last - Fetch the last entity from the relation # clear - Delete all the records from the relation end

Slide 41

Slide 41 text

Repository 実装例 # ORMやSQLの使用が可能 class ArticleRepository < Hanami::Repository def most_recent_by_author(author, limit: 8) articles.where(author_id: author.id) .order(:published_at) .limit(limit) end end article_repository = ArticleRepository.new article_repository.most_recent_by_author

Slide 42

Slide 42 text

Repositoryアンチパターン # ダメなケース(例: interactor層でRepositoryを下記のように呼び出す場合) class MostRecentByAuthorGetter attr_reader :repository def call(params) article_repository = ArticleRepository.new article_repository.where(author_id: params[:author][:id]) .order(:published_at) .limit(8) end end

Slide 43

Slide 43 text

タイトル MostRecentBy AuthorGetter ArticleRepository

Slide 44

Slide 44 text

Repositoryの呼び出し⽅ # 詳細の実装はRepositoryに隠蔽する事で呼び出し元のInteractorに # DIして使用できるようになる。 class MostRecentByAuthorGetter attr_reader :repository def initialize(repository) @repository = repository end def call(params) repository.most_recent_by_author(params[:id]) end End article_repository = ArticleRepository.new MostRecentByAuthorGetter.new(article_repository).call(params[:id])

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Interactor # /lib/bookshelf/interactors/book_interactors/add_book.rb require 'hanami/interactor' module BookInteractor class AddBook include Hanami::Interactor attr_reader :params attr_reader :repository expose :book def initialize(repository = BookRepository.new) @repository = repository end def call(params) @book = repository.create(Book.new(params)) end private # 参考: https://www.rubydoc.info/gems/hanami-utils/Hanami/Interactor/LegacyInterface # valid?はHanami::Interactorをincludeするとcall呼び出し前に実行するフックメソッド # valid? を実行する # valid? の戻り値が真値であれば、クラスに定義された #call を実行する # valid? の戻り地が偽値であれば、何もしない # Hanami::Interactor::ResultクラスはExposeで指定したキーをインスタンス変数とGetterとして持つ # callでHanami::Interactor::Result のインスタンスを返す def valid?(params) validate_result = Validations::Create.new(params).validate if validate_result.failure? error(validate_result.messages) end validate_result.success? end end end

Slide 47

Slide 47 text

Interactor # /lib/bookshelf/interactors/book_interactors/add_book.rb require 'hanami/interactor' module BookInteractor class AddBook include Hanami::Interactor attr_reader :params attr_reader :repository expose :book def initialize(repository = BookRepository.new) @repository = repository end def call(params) @book = repository.create(Book.new(params)) end private # 参考: https://www.rubydoc.info/gems/hanami-utils/Hanami/Interactor/LegacyInterface # valid?はHanami::Interactorをincludeするとcall呼び出し前に実行するフックメソッド # valid? を実行する # valid? の戻り値が真値であれば、クラスに定義された #call を実行する # valid? の戻り地が偽値であれば、何もしない # Hanami::Interactor::ResultクラスはExposeで指定したキーをインスタンス変数とGetterとして持つ # callでHanami::Interactor::Result のインスタンスを返す def valid?(params) validate_result = Validations::Create.new(params).validate if validate_result.failure? error(validate_result.messages) end validate_result.success? end end end

Slide 48

Slide 48 text

Interactor バリデーション成功時 # /lib/bookshelf/interactors/book_interactors/add_book.rb require 'hanami/interactor' module BookInteractor class AddBook include Hanami::Interactor attr_reader :params attr_reader :repository expose :book def initialize(repository = BookRepository.new) @repository = repository end def call(params) @book = repository.create(Book.new(params)) end private # 参考: https://www.rubydoc.info/gems/hanami-utils/Hanami/Interactor/LegacyInterface # valid?はHanami::Interactorをincludeするとcall呼び出し前に実行するフックメソッド # valid? を実行する # valid? の戻り値が真値であれば、クラスに定義された #call を実行する # valid? の戻り地が偽値であれば、何もしない # Hanami::Interactor::ResultクラスはExposeで指定したキーをインスタンス変数とGetterとして持つ # callでHanami::Interactor::Result のインスタンスを返す def valid?(params) validate_result = Validations::Create.new(params).validate if validate_result.failure? error(validate_result.messages) end validate_result.success? end end end

Slide 49

Slide 49 text

Interactor バリデーション成功時 # /lib/bookshelf/interactors/book_interactors/add_book.rb require 'hanami/interactor' module BookInteractor class AddBook include Hanami::Interactor attr_reader :params attr_reader :repository expose :book def initialize(repository = BookRepository.new) @repository = repository end def call(params) @book = repository.create(Book.new(params)) end private # 参考: https://www.rubydoc.info/gems/hanami-utils/Hanami/Interactor/LegacyInterface # valid?はHanami::Interactorをincludeするとcall呼び出し前に実行するフックメソッド # valid? を実行する # valid? の戻り値が真値であれば、クラスに定義された #call を実行する # valid? の戻り地が偽値であれば、何もしない # Hanami::Interactor::ResultクラスはExposeで指定したキーをインスタンス変数とGetterとして持つ # callでHanami::Interactor::Result のインスタンスを返す def valid?(params) validate_result = Validations::Create.new(params).validate if validate_result.failure? error(validate_result.messages) end validate_result.success? end end end

Slide 50

Slide 50 text

Interactor バリデーション失敗時 # /lib/bookshelf/interactors/book_interactors/add_book.rb require 'hanami/interactor' module BookInteractor class AddBook include Hanami::Interactor attr_reader :params attr_reader :repository expose :book def initialize(repository = BookRepository.new) @repository = repository end def call(params) @book = repository.create(Book.new(params)) end private # 参考: https://www.rubydoc.info/gems/hanami-utils/Hanami/Interactor/LegacyInterface # valid?はHanami::Interactorをincludeするとcall呼び出し前に実行するフックメソッド # valid? を実行する # valid? の戻り値が真値であれば、クラスに定義された #call を実行する # valid? の戻り地が偽値であれば、何もしない # Hanami::Interactor::ResultクラスはExposeで指定したキーをインスタンス変数とGetterとして持つ # callでHanami::Interactor::Result のインスタンスを返す def valid?(params) validate_result = Validations::Create.new(params).validate if validate_result.failure? error(validate_result.messages) end validate_result.success? end end end

Slide 51

Slide 51 text

Validation # /lib/bookshelf/interactors/book_interactor/validation/create.rb require 'hanami/validations' module BookInteractor::Validations class Create include Hanami::Validations validations do required(:title).filled(:str?) required(:author).filled(:str?) required(:unit_price).filled(:int?) end end end

Slide 52

Slide 52 text

Interactorのテスト # /lib/bookshelf/interactors/book_interactors/add_book.rb RSpec.describe BookInteractor::AddBook, type: :interactor do let(:book) { Book.new(params) } let(:repository) { double('BookRepository', create: book)} let(:interactor) { described_class.new(repository) } context 'with valid params' do let(:params) { Hash[ { title: 'TDD', author: 'Kent Beck', unit_price: 3500 } ]} it 'create a book' do interactor_result = interactor.call(params) expect(interactor_result.book.title).to eq params[:title] expect(interactor_result.book.author).to eq params[:author] expect(interactor_result.book.unit_price).to eq params[:unit_price] end # 永続化しているかどうかのテストはBookRepositoryが # createメソッドを呼んでいるかどうかをテストする context 'persistence' do # instance_double は 未定義のインスタンスメソッドをスタブした際にエラーを吐いてくれる let(:repository) { instance_double('BookRepository') } it 'persist a book' do expect(repository).to receive(:create) BookInteractor::AddBook.new(repository).call(params) end end end context 'with invalid params' do let(:params) { Hash[title: '', author: '', unit_price: -1]} it 'create a book' do interactor_result = interactor.call(params) expect(interactor_result.errors.first).not_to be_nil expect(interactor_result.errors.first[:title]).to eq ["must be filled"] expect(interactor_result.errors.first[:author]).to eq ["must be filled"] end end end

Slide 53

Slide 53 text

Controller # /apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action attr_reader :interactor before { |params| cast_unit_price(params) } expose :book, :errors def initialize(interactor = BookInteractor::AddBook.new) @interactor = interactor end def call(params) interactor_result = interactor.call(params[:book]) if interactor_result.success? @book = interactor_result.book redirect_to '/books’ else @errors = interactor_result.error_messages self.status = 422 end end private def cast_unit_price(params) return unless params[:book].key?(:unit_price) unit_price = params[:book][:unit_price] params[:book][:unit_price] = unit_price.to_i unless unit_price.empty? end end end

Slide 54

Slide 54 text

Controller # /apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action attr_reader :interactor before { |params| cast_unit_price(params) } expose :book, :errors def initialize(interactor = BookInteractor::AddBook.new) @interactor = interactor end def call(params) interactor_result = interactor.call(params[:book]) if interactor_result.success? @book = interactor_result.book redirect_to '/books’ else @errors = interactor_result.error_messages self.status = 422 end end private def cast_unit_price(params) return unless params[:book].key?(:unit_price) unit_price = params[:book][:unit_price] params[:book][:unit_price] = unit_price.to_i unless unit_price.empty? end end end

Slide 55

Slide 55 text

Controller # /apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action attr_reader :interactor before { |params| cast_unit_price(params) } expose :book, :errors def initialize(interactor = BookInteractor::AddBook.new) @interactor = interactor end def call(params) interactor_result = interactor.call(params[:book]) if interactor_result.success? @book = interactor_result.book redirect_to '/books’ else @errors = interactor_result.error_messages self.status = 422 end end private def cast_unit_price(params) return unless params[:book].key?(:unit_price) unit_price = params[:book][:unit_price] params[:book][:unit_price] = unit_price.to_i unless unit_price.empty? end end end

Slide 56

Slide 56 text

Controller # /apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action attr_reader :interactor before { |params| cast_unit_price(params) } expose :book, :errors def initialize(interactor = BookInteractor::AddBook.new) @interactor = interactor end def call(params) interactor_result = interactor.call(params[:book]) if interactor_result.success? @book = interactor_result.book redirect_to '/books’ else @errors = interactor_result.error_messages self.status = 422 end end private def cast_unit_price(params) return unless params[:book].key?(:unit_price) unit_price = params[:book][:unit_price] params[:book][:unit_price] = unit_price.to_i unless unit_price.empty? end end end

Slide 57

Slide 57 text

Controller # /apps/web/controllers/books/create.rb module Web::Controllers::Books class Create include Web::Action attr_reader :interactor before { |params| cast_unit_price(params) } expose :book, :errors def initialize(interactor = BookInteractor::AddBook.new) @interactor = interactor end def call(params) interactor_result = interactor.call(params[:book]) if interactor_result.success? @book = interactor_result.book redirect_to '/books’ else @errors = interactor_result.error_messages self.status = 422 end end private def cast_unit_price(params) return unless params[:book].key?(:unit_price) unit_price = params[:book][:unit_price] params[:book][:unit_price] = unit_price.to_i unless unit_price.empty? end end end

Slide 58

Slide 58 text

Controllerのテスト # /spec/web/controllers/books/crete_spec.rb RSpec.describe Web::Controllers::Books::Create, type: :action do let(:book) { Book.new(title: 'Confident Ruby', author: 'Avdi Grimm', unit_price: '3500') } let(:repository) { double('BookRepository', create: book) } let(:interactor) { BookInteractor::AddBook.new(repository) } # スタブしたInteractorをControllerにDI let(:action) { described_class.new(interactor) } context 'with valid params' do let(:params) { Hash[book: { title: 'Confident Ruby', author: 'Avdi Grimm', unit_price: '3500' }] } it 'create a book' do response = action.call(params) expect(action.book.title).to eq params[:book][:title] expect(action.book.author).to eq params[:book][:author] expect(action.book.unit_price).to eq params[:book][:unit_price].to_i expect(action.exposures[:book]).to eq Book.new(params[:book]) end it 'redirect the user to the books listing' do response = action.call(params) expect(response[0]).to eq 302 expect(response[1]['Location']).to eq('/books’) end end context 'with invalid params' do let(:params){ Hash[book:{ hoge: 'fuga' }] } it 'returns HTTP Client error' do response = action.call(params) expect(response[0]).to eq(422) expect(action.errors).not_to be_nil end end end

Slide 59

Slide 59 text

Controllerのテスト # /spec/web/controllers/books/crete_spec.rb RSpec.describe Web::Controllers::Books::Create, type: :action do let(:book) { Book.new(title: 'Confident Ruby', author: 'Avdi Grimm', unit_price: '3500') } let(:repository) { double('BookRepository', create: book) } let(:interactor) { BookInteractor::AddBook.new(repository) } let(:action) { described_class.new(interactor) } context 'with valid params' do let(:params) { Hash[book: { title: 'Confident Ruby', author: 'Avdi Grimm', unit_price: '3500' }] } it 'create a book' do response = action.call(params) expect(action.book.title).to eq params[:book][:title] expect(action.book.author).to eq params[:book][:author] expect(action.book.unit_price).to eq params[:book][:unit_price].to_i expect(action.exposures[:book]).to eq Book.new(params[:book]) end it 'redirect the user to the books listing' do response = action.call(params) expect(response[0]).to eq 302 expect(response[1]['Location']).to eq('/books’) end end context 'with invalid params' do let(:params){ Hash[book:{ hoge: 'fuga' }] } it 'returns HTTP Client error' do response = action.call(params) expect(response[0]).to eq(422) expect(action.errors).not_to be_nil end end end

Slide 60

Slide 60 text

ViewとTemplate require 'hanami/view' module Articles class Index include Hanami::View end class AtomIndex < Index format :atom end end Hanami::View.configure do root 'app/templates' end Hanami::View.load! articles = ArticleRepository.new.all Articles::Index.render(format: :html, articles: articles) # => This will use Articles::Index # and "articles/index.html.erb" Articles::Index.render(format: :atom, articles: articles) # => This will use Articles::AtomIndex # and "articles/index.atom.erb" Articles::Index.render(format: :xml, articles: articles) # => This will raise a Hanami::View::MissingTemplateError

Slide 61

Slide 61 text

ViewとTemplate(登録失敗時: 登録フォーム画⾯) # apps/web/template/books/new.html.erb

Add book

<% unless error_messages.empty? %>

There was a problem with your submission

    <% error_messages.each do |message| %>
  • <%= message %>
  • <% end %>
<% end %> <%= form_for :book, '/books' do div class: 'input' do label :title text_field :title end div class: 'input' do label :author text_field :author end div class: 'input' do label :unit_price number_field :unit_price end div class: 'controls' do submit 'Create Book’ end end %> # apps/web/views/books/create.rb module Web::Views::Books class Create include Web::View template 'books/new’ def error_messages error_messages = [] errors.each do |error| error_messages = error.map{|k, v| error_format(k, v) } end error_messages end private def error_format(key, values) "#{key} #{values.join(",")}" end end end

Slide 62

Slide 62 text

ViewとTemplate(登録失敗時: 登録フォーム画⾯) # apps/web/template/books/new.html.erb

Add book

<% unless error_messages.empty? %>

There was a problem with your submission

    <% error_messages.each do |message| %>
  • <%= message %>
  • <% end %>
<% end %> <%= form_for :book, '/books' do div class: 'input' do label :title text_field :title end div class: 'input' do label :author text_field :author end div class: 'input' do label :unit_price number_field :unit_price end div class: 'controls' do submit 'Create Book’ end end %> # apps/web/views/books/create.rb module Web::Views::Books class Create include Web::View template 'books/new’ def error_messages error_messages = [] errors.each do |error| error_messages = error.map{|k, v| error_format(k, v) } end error_messages end private def error_format(key, values) "#{key} #{values.join(",")}" end end end

Slide 63

Slide 63 text

ViewとTemplate(登録失敗時: 登録フォーム画⾯) # apps/web/template/books/new.html.erb

Add book

<% unless error_messages.empty? %>

There was a problem with your submission

    <% error_messages.each do |message| %>
  • <%= message %>
  • <% end %>
<% end %> <%= form_for :book, '/books' do div class: 'input' do label :title text_field :title end div class: 'input' do label :author text_field :author end div class: 'input' do label :unit_price number_field :unit_price end div class: 'controls' do submit 'Create Book’ end end %> # apps/web/views/books/create.rb module Web::Views::Books class Create include Web::View template 'books/new’ def error_messages error_messages = [] errors.each do |error| error_messages = error.map{|k, v| error_format(k, v) } end error_messages end private def error_format(key, values) "#{key} #{values.join(",")}" end end end

Slide 64

Slide 64 text

ViewとTemplate(登録成功時: 本⼀覧画⾯) # apps/web/template/books/index.html.erb

All books

<% if books.any? %>
<% books.each do |book_presenter| %>

<%= book_presenter.title %>

<%= book_presenter.author %>

<%= book_presenter.total_price %>

<% end %>
<% else %>

There are no books yet.

<% end %> New book # apps/web/views/books/index.rb module Web::Views::Books class Index include Web::View # SystemStackError (stack level too deep). # def books # books.map{|book| BookPresenter.new(book) } # end def books locals[:books].map{|book| BookPresenter.new(book) } end end end

Slide 65

Slide 65 text

ViewとTemplate(登録成功時: 本⼀覧画⾯) # apps/web/template/books/index.html.erb

All books

<% if books.any? %>
<% books.each do |book_presenter| %>

<%= book_presenter.title %>

<%= book_presenter.author %>

<%= book_presenter.total_price %>

<% end %>
<% else %>

There are no books yet.

<% end %> New book # lib/bookshelf/presenters /book_presenter.rb require 'hanami/view’ class BookPresenter include Hanami::Presenter attr_reader :book def initialize(book) @book = book end def title book.title end def author book.author end def total_price "税込価格: #{book.total_price.round}円" end end

Slide 66

Slide 66 text

07 Hanamiを 使⽤してみての感想

Slide 67

Slide 67 text

感想1 Hanamiは各レイヤーそれぞれにmoduleが用意されており、 このModuleが提供してくれるインターフェースはイニシ ャライザとCallの2つというシンプルさとシンプルさ故の カスタマイズ性の高さ、そしてカスタマイズ性が高いが故 に開発者の設計思想をしっかり反映できるのがメリットだ と感じた

Slide 68

Slide 68 text

Callの⼀択である事で感じたメリット CallメソッドのみがPublicに呼び出せる共通の インターフェースであるため、DIのみを意識しておけば抽 象化も行いやすい (RepositoryとInteractorについては工夫が必要そう)

Slide 69

Slide 69 text

参考⽂献 ・ Clean Architecture 達人に学ぶソフトウェアの構造と設計 https://www.amazon.co.jp/dp/B07FSBHS2V/ref=dp-kindle- redirect?_encoding=UTF8&btkr=1 ・HanamiはRubyの救世主(メシア)となるか、愚かな星と散るのか https://magazine.rubyist.net/articles/0056/0056-hanami.html ・Laravelで実践クリーンアーキテクチャ https://qiita.com/nrslib/items/aa49d10dd2bcb3110f22 ・クリーンアーキテクチャ完全に理解した https://gist.github.com/mpppk/609d592f25cab9312654b39f1b357c60

Slide 70

Slide 70 text

ご清聴いただきありがとうございました Thank You We are Hiring ! https://recruit.fusic.co.jp/