🌌

~私の1年間のアーキテクチャ遍歴~

2024/05/25に公開1

私のアーキテクチャ遍歴について📝

この記事を書くきっかけ📗

プログラミングに日常的に触れ合うようになってはや1年になりますが、周りにClean Architectureに関して学習を進めている方がいたり、MVVMで開発をしようとしてる方がいたりとさまざま。そこで自分のこれまでのアーキテクチャ遍歴についてある程度まとめてそれぞれのメリットデメリット、形式などを語っていきたいとおもいます。
独断と偏見等が散見されると思うので参考程度にみてほしいです。

アーキテクチャ初心者故、どんどん突っ込んでもらえると幸いです!

プログラミングを始めて数ヶ月📅

アーキテクチャに関しては全くわからない状態でした。
アーキテクチャというと私の中ではどのような技術を使っているのか、程度でたとえばSwiftだとUIフレームワークにSwiftUIを使っていて、version, package管理等でXcodegenを使っているよ〜だと思っていました。(当時はFlutterのRiverpodって何??のレベル)
また、「ディレクトリ分けることに何の意味があるの?」状態でした。
今考えると恥ずかしいです💦

ベタ書きについて🖊️

まずは簡単な例を書いてみましょう。
下では、どこかしらで入力されてきたデータをボタンを押すことでFirebaseのFirestoreに保存しようとしています。
ここで、ベタガキのメリットデメリットを簡単に挙げていきます。

Sample.swift
let db = Firestore.firestore()
@State title: String = "アーキテクチャにとらわれない系エンジニアに俺はなる!"
@State description: String = "hogehogehogehogehogehogehogehogehogehogehoge"

Button("SetArticleData") {
    let articleRef = db.collection("article")
    articleRef.document("Betagaki").setData([
        "title" = title,
        "description" = description
    ])
}

メリット

  1. 初学者は、どこになんのファイルがあるかの把握に慣れていないので読みやすく感じる。
  2. 同じファイルに参考になるコードが書かれているので他のコードを参考にしやすいことから開発スピードが上がる。
    僕もそうでしたが、ロジックを同じファイルに書き込むといつでも仕様が確認できて便利だと感じてしまってそれが恒常のものであると錯覚してしまいます。また、それが地味に下手に調べながら分けるより開発速度が速いので周りで教えてくれる人がいなかったりすると「良いこと」と思ってしまいがちです。

デメリット

  1. 分けないことがデフォルトになってしまい,実務に入った途端に混乱する。
  2. 実は見づらいコードになってしまっている。
  3. チーム開発(ハッカソンなど)になるとTask管理などが微妙だとコンフリクト祭りが開催される。
    ここに関してはほぼ実体験になりますが、MVCが来た瞬間に「ふぁっ?!」となりました。
    規模が少しづつ大きくなるにつれ、「どこに、何があるのか」を把握することが難しくなり、ViewとLogicを分けていないと逆に参考にしづらくなります。
    また、初心者、中級者ハッカソンに出た時に、チーム内でAさんはContentViewのUI部分、BさんはContentViewのLogic部分を触ってしまい、Githubもあんまり理解していないからとりあえず、色々試してみる。なんてことをしてしまうと、、、
    このように、ベタ書きはメリットを凌駕するほどのデメリットが存在します。

実務に入るようになって(約半年):MVC, MVVM

上記にもあるように、ベタ書き状態でいざRailsの実務に参戦することになりました。
そこではやはり、がっつり苦労することになります。まず、MVCとはなんぞや!
Model, View, Controllerに分けられるよ〜と言われても...

MVCとは

Railsなどで用いられるアーキテクチャでModel, View, Controllerに分類されます。
MVCは基本的にはSwiftで用いることはありません。
Controllerにはサーバーサイドのロジック等が書かれるので少しSwiftで使うのは微妙...
下に書き方の例を記載しておきます。

Model

ArticleModel.swift
struct ArticleModel {
    let id: UUID
    let title: String
    let description: String
    
    init(id: UUID, title: String, description: String) {
        self.id = UUID()
        self.title = title
        self.description = description
    }
}
// またここにはバリデーション等のビジネスロジックが書かれる。

Controller

ArticleController.swift
class ArticleController {
    let db = Firestore.firestore()

    func setData(articleModel:ArticleModel) {
        let articleRef = db.collection("article")
        articleRef.document("Betagaki").setData([
            "title" = articleModel.title,
            "description" = articleModel.description
        ])
    }
    // 他にも取得したデータ配列の結合など行う。
}

View

ArticleView.swift
import SwiftUI

struct SwiftUIView: View {
    @State var title: String = ""
    @State var description: String = ""

    let articleController = ArticleController

    var body: some View {
        TextField("Title", text: $title)
        TextField("Title", text: $description)
        Button("submit") {
            articleController.setData(articleModel: ArticleModel(title: title, description: description))
        }
    }
}

#Preview {
    SwiftUIView()
}

上記はSwiftで書いているので後に書くMVVMと重なってしまいます。
細かく知りたい方はぜひMVVM,MVCの違いについて調べてみてください。

ViewModel

これに関しては何となくControllerと同じ認識でも大丈夫です。
Viewの状態管理等における責務をになっており、

MVC, MVVMのメリット

  1. 中小規模のプロダクトにおいて、可読性や保守性の観点から優れている
  2. 初学者が始めに挑戦する際に、ControllerやViewModelといった、大きな受け皿が存在する
  3. Directoryの数が多くないため、キャッチアップコストが低い
  4. 実際に多くのプロジェクトで使われており、技術系の記事を調べる際に多く登場する

MVC, MVVMのデメリット

  1. 規模が大きくなるとControllerやViewModelが肥大化してしまい、可読性、保守性が損なわれる
  2. 責務に関して不明瞭でオンボーディングコストがすこし大きくなる

と、ここまででざっと半年間のアーキテクチャ遍歴について書いていきましたがあと半年分が残っています。

MVVM + Service(9ヶ月目程度)

ここでようやくCleanArchitectureの存在を知り学習を始めました。
といっても初めからCleanArchitectureの思想通り、完璧な構成を作ることはできず、当時参加していたTechAccelでメンターさんに質問しながらこのようなアーキテクチャで開発を行いました。

Serviceとは?

上記のMVVMの中に大きな問題点があるとするならば、それはViewModelの肥大化でしょう。
それに対してServiceを用いる事でその肥大化を防ぐとともに、ロジック操作を行います。

当時は時間の関係でAuthに関してのServiceしか作っていなかったのですが、Controllerに対してビジネスロジックなどを提供することを意識することが必要になります。

ViewModel

AuthViewModel.swift
class AuthViewModel {
    let authService = AuthService()

    func islogedIn(email: String, password: String) async -> Bool {
        return await authService.login(email: email, password: password)
    }    
}

Service

AuthService.swift
class AuthService {

    func islogedIn(email: String, password: String) async -> Bool {
        do {
            try await Auth.auth().signIn(withEmail: email, password: password)
            isAuthenticated = true
            return true
        } catch {
            return false
        }
    }
}

ここであえて、同じメソッドを定義している意図としては、ClearnArchitectureにおいての依存関係を考えた際に、後でも説明しますがUIからService層に依存してはならず、間のPresentation層(Presenter)に依存させるためにViewModelを擬似的なPresentation層として配置し、捉える事で解決しようとしています。

Service層を挟むことのメリット

  1. 実際にfuncをViewで使用する際に、CleanArchitectureの思想から考えるとServiceにViewは依存していない
  2. 可読性、保守性に優れている

デメリット

  1. 初見では同じ名前の関数,メソッドをViewModelで定義しているので一見無駄なことをしてるように見えてしまう
  2. Repository層を持たないため、同じようにService層が肥大化してしまう

これらが大まかな問題になるかな、と思います。

CleanArchitecture (~現在)

ここでようやくCleanArchitechtureを用いて開発を行うようになります。

CleanArchitectureとは一体何者か?

少し話は難しくなりますが、CleanArchitechtureはこれまでのMVCやMVVM,ベタ書きとは少し異なります。
たとえば、MVVMに関しては下記のようなDirectory構成を持っています。

また、MVCではこうなります。

しかしCleanArchitectureはこれまでのDirectory構成をほぼ直接的に表すものではなくプロダクトの構成における「思想」のようなものになります。
100%正しいものがあるわけではなく、その代わりに有名な図がこれになります

完結に説明すると、プロダクトにおいて変更がされないようなものにおいては中心に配置しそこから外側に向けて変更が多いものを配置していこうという考え方になります。単一方向への依存性を考えることで保守性や可読性において大きなメリットを生むものになっています。
これに関してはまた詳しく書いた記事を執筆しようと思っているのでぜひ読んでみてください。

Architectureを学んで考えるようになったこと

コードの質が非常に上がったと思っています。(元が低すぎたというのもありますが、、、)
また、そのおかげでコードの質に関して考えるようにもなり、実際にアーキテクチャについて考える事で開発がたのしくなりました。
ぜひ一度、機能実装だけでなく、こちらにも目を向けてみてください。

皆さんもぜひより良いアーキテクチャライフを!

Jboy王国メディア

Discussion

JboyHashimotoJboyHashimoto

技術同人誌でも出せるような内容ですね!
素晴らしい🏆🏆🏆🏆🏆
CleanArchitecture, MVVM経験がありますが、そこの職場の文化で使っていたので私も使ってました。
レイヤーを分けてることのメリットやよくあるデザインパターンを使うメリットの解説がわかりやすいですね。コードもあるのはありがたい。