クソアプリをMVCからDDDに無理やり直してみた

# 今回作ったWEBサービス

![oji.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/920917/fbdaa970-c118-f541-1942-705842af0c52.gif)
こんにちは！ちゃきです！
定期的にクソサービスを個人開発して世の中のドメイン無駄遣いしてます！
そろそろQiita更新しないとヤバいと思い書きました。

転職前も転職後もPHPをほとんど使っています。
しかし社内のサービスはほとんどrailsで作られているため、今後の為にrailsの勉強の一環として
今回のサービスを作りました。
てか社内にlaravelエンジニア全然いない。

## どんなサービスか？
登録してある若者言葉をおじさんでもわかる単語に変換してくれる***チャットアプリ***です。
おじさんの言葉も若者がわかるように変換します。
最初から登録してある単語以外にも追加はできます。

|　　　　　　　　  　　　　　　　　   　　　　　　　　　　　　　　　　　　　　　　　　　
|:-:|
|![スクリーンショット 2022-09-03 10.23.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/920917/987838f0-6db9-c790-a299-7cdd9d32b67b.png)

 ## なんで作ったか？
まぁ、IT業界なんで自分より若い人多いです。
そして若者が何言ってるかわからない。
居酒屋で2~3分で良いから盛り上がって会話の糸口になるものが欲しい。
そう思い開発しました。
あと、「おじさん構文」なんて言葉も出てきてますしね。
おじさんが使いそう一般的な顔文字も封じ込めてます。
顔文字使ったら無理矢理削除して送られます。

我ながらくだらな過ぎるサービス。

## 使用言語
ruby 2.7
rails 6
Vue.js

## 追加したGEMやライブラリ
capistrano・・・自動デプロイ
rubocop・・・コード綺麗にしてくれる
vue.croppie・・・画像クロッピング。公式ドキュメントが間違っていたりで疲れた。
rspec・・・テスト
fog-aws・・・S3への画像保存

capistranoは明示的にブランチを指定してあげないと「master」ブランチを見に行くので
「main」ブランチをデプロイしたければdeploy.rbに以下を追記
```config > deploy.rb
set :branch, ENV['BRANCH'] || "main"
```

## 頑張ったところ
今回は以下に重点を置いた学習のアウトプットです。
***・railsのお作法に慣れる***
***・DDD及びクリーンアーキテクチャの雰囲気を掴む***(全ての処理はリファクタリングしてません)
***・インフラ構築の理解を深める***
その為、あえて最初はMVCで作ってから主要機能のみDDDに載せ替えてみました。

[参考記事:ドメイン駆動設計の比類なきパワーでRailsレガシーコードなど大爆殺したるわあああ！！！](https://qiita.com/MinoDriven/items/3c7db287e2c66f36589a)


フロント側は安定のやる気のなさでBootstrapで手抜きしてます。
ほぼCSS書いてません。（むしろ何が悪いのかわからない）
バリデーションメッセージも手抜き。。。


## クラス図
![My First Board.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/920917/ac3270df-16c2-6fb9-2eb8-228dcac49f66.jpeg)
### 主なリファクタリングは以下の通り
・コントローラーから直接ActiveRecordは使わないようにした。
・コントローラー内のコアなロジック（ビジネスロジック）はドメイン層へ移動。
・Valueオブジェクトにより、インスタンスが生成された瞬間から完全体＆テストが書きやすい。
・CQRSパターンを使用してDBからの取得系の処理と登録、更新系の処理を分けた。
以下、一部抜粋

### Valueオブジェクト


```Name.rb
class Name
    attr_reader :value

    def initialize(value)
        @value = value
    end

    def self.new(value)
        return nil,'name is must be a string' unless value.is_a?(String)
        super(value)
    end
end
```
### Aggregate(更新系カラムの集約)
```user_aggregate.rb
require './domains/value_object/user/name.rb'
require './domains/value_object/user/password.rb'
require './domains/value_object/user/adult_flg.rb'
require './domains/value_object/user/avatar.rb'


class UserAggregate

    def name(value)
        Name.new(value)
    end

    def password(value)
        Password.new(value)
    end

    def adult_flg(value)
        AdultFlg.new(value)
    end

    def avatar(value)
        Avatar.new(value)
    end

end
```
その他、Write○○.rbやRead○○.rbにはCRUD処理のR(クエリ)、CUD（コマンド）のロジックや定義を分けてます。
これだけでもコントローラー内の直接のActiveRecordの依存は防ぎ、密結合低凝集の最悪の状態を防げます。
以下、一部コントロラー内のリファクタリング前と後のBefore Afterのコードを抜粋。
このサービス内でも重要なロジックの書かれている場所です。
内容の説明は重要ではないので詳しくは省きますが、
要はおじさんの言葉を若者言葉に、若者言葉をおじさん言葉に変換する処理です。
もうホント個人開発なのでやばいです。汚い
### Before
```message.rb
def create_young_message(message, word)
    new_message = message.convert_young_message.gsub!(word.term, word.conversion)
    message.update!(convert_young_message: new_message)
  end

  def create_old_message(message, word)
    new_message = message.convert_old_message.gsub!(word.conversion, word.term)
    message.update!(convert_old_message: new_message)
  end

  def create
    @message = Message.create!(message_params)
    @message.update!(convert_old_message: message_params['content'], convert_young_message: message_params['content'])
    @room = Room.find_by(id: message_params[:room_id])
    @words = Word.all

     
    @words.map do |word|
      if @message.content.include?(word.term) && @current_user.adult_flg == false # 若者が発した言葉のみ変換。おじさんの背伸びは変換しないであげる
        create_young_message(@message, word)
      elsif @message.content.include?(word.conversion) && @current_user.adult_flg == true  # おじさんが発信したら若者言葉に変換。若者の優しさは変換しない
        create_old_message(@message, word)
      end
    end

    # js側にデータ渡してる
    RoomChannel.broadcast_to(@room, message_id: @message.user_id, user: @user, message_old: @message.template,message_young: @message.template_young)
  end
```

### After
```message.rb
def create

    @words = @wcs.get_all
    @message = @mcs.create(message_params)
    @mcs.update(@message)
    @room = @rqs.find(@current_user,message_params[:room_id])
    @mcs.map_to_update(@words,@message,@current_user)

    # js側にデータ渡してる
    RoomChannel.broadcast_to(@room, message_id: @message.user_id, user: @user, message_old: @message.template,message_young: @message.template_young)
  end
```
ここでのupdateやfindはActiveRecordのメソッドではないです。
自分で作った、CommandServiceやQueryServiceのメソッドを呼び出しています。
だいぶスッキリしましたね。

## インフラ構成
![AWS.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/920917/a77ecdd5-707e-d66e-790e-495c7002ed96.jpeg)
お金もったいないのでNATは速攻外しました。
誰も使わなそうなドメイン名なのでドメインは1円でお名前.comで購入。
AWSのRout53のNSレコードをお名前.comに登録して使ってます。
## まとめ
正直この程度のクソサービスならDDDやクリーンアーキテクチャ使わずにMVC使ってmodelとtableの密結合にどっぷり甘えて
作った方が良いと感じました。ディレクトリも増えないし、工夫すれば別にそこまで乱雑なコードにもならないと思います。
そもそもrailsはそういう作りがウリで急成長を遂げたのに無理矢理アーキテクチャ変えたらそりゃ大変だと感じました。
viewさえもmodelに依存してるのでActiveRecordを完全に使わないという選択肢をとった場合はformさえも書き換えなきゃいけなかったりでヤバいですね。
なので一番はrailsの良さを殺さず共存できる形が良いと感じました。
やるならフレームワーク使わずプレーンなrubyでやるべき。
あとrubyはインターフェース作れないのでそれっぽい振る舞いの抽象クラス作るしかないのでlaravelの方が設計しやすいかもなと感じました。
改めてPHPの方が自分は好きだなぁと再認識。
拙いのでご意見ご指摘お願い致します。