Upgrade to Pro — share decks privately, control downloads, hide ads and more …

異なる設計思想のフレームワークを経験して得た学び

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

 異なる設計思想のフレームワークを経験して得た学び

Avatar for amekuhideki

amekuhideki

August 21, 2026

Other Decks in Programming

Transcript

  1. なぜDjangoは「app」で切るのか Laravel のディレクトリ構成 app/ ├ Http/Controllers/ │ ├ UserController.php │

    └ PostController.php ├ Models/ │ ├ User.php │ └ Post.php └ Services/ routes/ └ web.php database/ └ migrations/ Laravel 役割(レイヤ)ごとに 置き場所が決まっている Controller は Controllers/ へ、 Model は Models/ へ格納。 コンポーネントの関⼼事が明確に分離されま す。 「何を作るか」が決まれば、ファイルを作成す べき場所に迷うことがありません。 8
  2. なぜDjangoは「app」で切るのか Django のディレクトリ構成 - 並べて⾒ると Laravel Django app/ ├ Http/Controllers/

    │ ├ UserController.php │ └ PostController.php ├ Models/ │ ├ User.php │ └ Post.php database/migrations/ users/ ├ models.py ├ views.py ├ admin.py └ migrations/ posts/ ├ models.py └ views.py 役割(レイヤ)で切り、その中に機能が並ぶ 機能(app)で切り、その中に役割が並ぶ Controllersディレクトリ内にUserもPostもいる usersディレクトリ内にmodelsもviewsもいる 同じようにモデルやビューを持つフレームワークでも、ディレクトリのまとめ⽅が真逆 9
  3. なぜDjangoは「app」で切るのか Laravelのマイグレーションファイル管理 単⼀のディレクトリ管理 database/migrations/ ├ 2026_01_10_000000_create_users_table.php ├ 2026_02_03_000000_create_posts_table.php ├ 2026_03_15_000000_add_status_to_posts.php

    └ 2026_04_02_000000_add_index_to_users.php プロジェクト全体で1つの場所にマイグレーションを 集約 ⼀本の⼀元的な時系列 ファイル名に付与された⽇時プレフィックス (YYYY_MM_DD_…)により、すべてのテーブル変更履 歴が⾃動的に並びます。 • • 全変更の実⾏順序が直感的に理解しやすい ロールバックの位置が分かりやすい 11
  4. なぜDjangoは「app」で切るのか Djangoのマイグレーションファイル管理 appごとに独⽴した連番 users/migrations/ ├ 0001_initial.py └ 0002_alter_user_email.py posts/migrations/ ├

    0001_initial.py ├ 0002_add_status.py └ 0003_add_index.py マイグレーション履歴がapp単位に完全に分離して管 理されます。 全体を貫く時系列は存在しない Laravelのような「プロジェクト全体で1本の履歴」 は、どこにもない。 12
  5. なぜDjangoは「app」で切るのか appをまたぐ順序はdependenciesが繋ぐ 時系列ではなく、依存関係で並ぶ # posts/migrations/0001_initial.py 他のappのモデルにFKを張ると、makemigrationsが 「postsはusersの後」という関係を⾃動で記録する。 class Migration(migrations.Migration): dependencies

    = [ ("users", "0001_initial"), ] operations = [ ... ] 実⾏順はDjangoが⾃動決定 dependencies に書かれた依存関係をもとに、Django が実⾏順を⾃動で解決する。 • • ファイル名の⽇時に縛られない柔軟な管理 複数アプリにまたがる複雑な依存も安全に解決 13
  6. なぜDjangoは「app」で切るのか 実際、公式のappの定義にもこう書かれている 引⽤ (英⽂) 和訳 "The term application describes a

    Python package that provides some set of features. Applications may be reused in various projects." 「アプリケーションとは、何らかの機能⼀式を提供す るPythonパッケージのことである。アプリケーショ ンは様々なプロジェクトで再利⽤されうる」 出典 Django公式ドキュメント (Applications) docs.djangoproject.com/en/stable/ref/applications/ 15
  7. なぜDjangoは「app」で切るのか だからマイグレーションもappに同梱される 配布される app models.py views.py migrations/ ← スキーマも⼀緒に 別のプロジェクト

    pip install migrate を実⾏すれば 特別な追加設定なしで、必要なデータベーステーブル まで⾃動で⼀気に揃います。 ⚠ テーブル定義が本体の migrations に混ざっていたら、app を pip install しても動かせない ※ Laravelもパッケージから loadMigrationsFrom() で配布は可能。違いは「パッケージ開発者向けの opt-in」か「標準」か 16
  8. なぜDjangoは「app」で切るのか Laravel出⾝者が躓いた4つのポイント 躓き 簡単な説明 ① アプリの粒度 どこまでを1アプリにすればいいのか判断がつかない。 ② INSTALLED_APPS登録忘れ 作っただけでは動かない。Laravelなら作れば⾃動で認識される。

    ③ appをまたぐ循環インポート users ⇄ posts で詰まる。 ④ apps.py / AppConfig 何のために存在するのか最初は意味不明。 それぞれどう向き合ったのか、次のスライドで⾒ていきます 17
  9. なぜDjangoは「app」で切るのか ⼀番苦労したのは「粒度」の判断 慣れれば解決した ②④ 登録忘れは⼿順が⾝につけば起きない。 AppConfigは「appの設定を書く場所」と役割を知れば⼾惑わない。 循環インポートは「機能で切った代償」 ③ ① レイヤ切りのLaravelでは起きない問題。

    解法は⽂字列参照 FK("users.User") と settings.AUTH_USER_MODEL。 粒度だけは、慣れでは解決しなかった 何個作っても「この切り⽅でいいのか」に確信が持てない 「単体で配布できるか?」が粒度の判断基準 18
  10. なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」はNULLで表すのが常識だと思っていた • SQLの世界では、「値がない」は NULL という専⽤の状態で表す • 空⽂字 "" は「⻑さ0の⽂字列」という⽴派な値であり、「値がない」とは別物

    • 多くのDB設計では、NULLと空⽂字を区別して扱う • Laravelもこの流儀に乗っていて、フォームの空⽂字を⾃動でNULLに変換する仕組み (ConvertEmptyStringsToNull)が標準で⼊っている Djangoを扱ってみて、この常識が通⽤しなかった 21
  11. なぜDjangoのモデルは管理画⾯まで作れるのか 実際、DB(MySQL)の公式もこう⾔っている 引⽤(和訳) 「0や空⽂字は実際の値であるのに対し、 NULLは『値を持たない』ことを意味する」 ― MySQL 8.4 Reference Manual

    (Working with NULL Values) 参考ソース(公式ドキュメント) https://dev.mysql.com/doc/refman/8.4/en/working-with-null.html https://dev.mysql.com/doc/refman/8.4/en/problems-with-null.html 22
  12. なぜDjangoのモデルは管理画⾯まで作れるのか Laravelのnullable() は「DBの話」だけをしている DB層:スキーマの定義 // migration 宣⾔しているのは「DBカラムがNULLを許可する」と いうデータベース仕様のみです。 Schema::table("users", function

    (Blueprint $table) { $table->string("nickname")->nullable(); }); 検証層:アプリケーションの仕様 ⼊⼒検証(バリデーション)は FormRequest など、 別の場所で別途記述します。 マイグレーションとバリデーションは独⽴した世界で す。 23
  13. なぜDjangoのモデルは管理画⾯まで作れるのか Djangoでは「null=」と「blank=」が分かれている 2つの異なる宣⾔が同居 class User(models.Model): nickname = models.CharField( 同じフィールド定義の中に、DB⽤の設定(null)とアプ リケーション検証⽤の設定(blank)が同居していま

    す。 max_length=50, null=False, # DB: NULLを許可しない blank=True, # 検証: 空入力を許可 ) 素朴な疑問:「なぜ冗⻑なのか?」 「なぜ同じような役割のものが2つもあるのか?」「1つ にまとめられないのか?」という疑問が⽣まれます。 24
  14. なぜDjangoのモデルは管理画⾯まで作れるのか 1つの定義が2つのレイヤを語る nickname = CharField(null=, blank=) ↓ ↓ null= →

    DB層 blank= → フォーム / 検証層 カラムが NULL を格納できるか ⼊⼒フォームで空を許すか (データベース‧スキーマの仕様) (アプリケーション‧バリデーションの仕様) 1つのフィールド定義が、DBとフォームの2つのレイヤを同時に定義している 25
  15. なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」が2種類あると、経路で挙動がズレる フォーム経由 API‧シェル経由 未⼊⼒は空⽂字 "" として保存される 未設定は NULL として保存されうる

    Djangoのフォームは空⼊⼒を "" に正規化する null=True なら None がそのままDBに⼊る 同じ「未⼊⼒」なのに、DBには "" と NULL の2通りの値が混在する → 検索‧⽐較‧ユニーク制約が壊れやすくなる 26
  16. なぜDjangoのモデルは管理画⾯まで作れるのか だから公式ドキュメントはこう⾔っている “Avoid using null on string-based fields such as

    CharField and TextField.” ーーCharFieldやTextFieldのような⽂字列フィールドでは、nullの使⽤を避けよ ― Django公式ドキュメント(Model field reference / null) ー https://docs.djangoproject.com/en/stable/ref/models/fields/ ⽂字列フィールドに null=True を使わない 「値がない」の表現を空⽂字 "" の1つに寄せる、という Djangoの規約です。 【有名な例外】 unique=True の任意項⽬ この場合は null=True が正解です。NULLは⼀意性制約に 数えられませんが、空⽂字 "" は2件⽬で⼀意性違反にな ります。 27
  17. なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」をどこに寄せるか Laravel Django • 「値がない」は NULL に寄せる • •

    フォームの空⽂字は ConvertEmptyStringsToNull ミドル ウェアが⾃動でNULLに変換 ⽂字列の「値がない」は空⽂字 "" に寄 せる • フォームの未⼊⼒は "" として保存し、 ⽂字列に null=True は付けない DBの流儀(値なし=NULL)に合わせた • Webの流儀(HTTPにNULLは存在しな い、空欄は空⽂字)に合わせた • 28
  18. なぜDjangoのモデルは管理画⾯まで作れるのか admin.site.register で管理画⾯が完成する # posts/admin.py この数⾏を記述するだけで、データ管理に必要な 基本機能がすべて⼿に⼊る from django.contrib import

    admin from .models import Post  データの⼀覧表⽰ admin.site.register(Post)  新規データ作成‧詳細編集フォーム  レコードの個別‧⼀括削除  モデル定義に基づく⾃動バリデーション Laravelで数週間かけていたものが、この5⾏だった 31
  19. なぜDjangoのモデルは管理画⾯まで作れるのか なぜ? ― モデル定義がUIの材料を全部持っている フィールド型 CharField / DateField … 

    ⼊⼒ウィジェットが決まる 検証ルール blank= / max_length …  フォームバリデーションが決まる 表⽰名 verbose_name  ラベル‧画⾯表記が決まる モデルに仕様が揃っているから、UIは「導出」できる 32
  20. なぜDjangoのモデルは管理画⾯まで作れるのか なぜ管理画⾯まで作れるのか Djangoのモデルは “DBスキーマ定義” ではなく “ドメイン定義” DBスキーマ定義 ドメイン定義 単にデータベースのテーブル構造を定義するもの。 システム的なデータの格納ルールに限定されます。

    型‧検証‧表⽰名まで含めた「仕様の置き場所」。 開発や運⽤に必要なビジネスルールを表現します。 だから、null= と blank= が同居し、管理画⾯まで導出できる データベース制約(null)と管理画⾯の必須バリデーション(blank)を、⽭盾なく⼀つのモデルに集約可能です。 33
  21. なぜDjangoのモデルは管理画⾯まで作れるのか 「値がない」が2つあるコード 課題:表現が2つある状態 解決:Djangoの規約 # 「値がない」の表現が2つ(Noneと"")あると… 「⽂字列の空は空⽂字に寄せる」 if value is

    None or value == "": # 未入力として扱う # この冗長な分岐が、あらゆる境界に増殖する 表現を「空⽂字("")」の1つに統⼀することで、シス テム全体に散らばる複雑な条件分岐そのものを綺麗に消 し去ることができます。 制約(規約)は、コードを縛るためでなく、分岐を消すためにある 34
  22. なぜDjangoのモデルは管理画⾯まで作れるのか 管理画⾯を「作らなくてよくなった」 Laravel • • • 管理画⾯はBladeテンプレートで1画⾯ ずつ⼿作り ⼀覧‧検索‧編集フォーム…モデルごと にビューを書き、項⽬が増えるたびに画

    ⾯も直す 「管理画⾯の実装」がスケジュールの中 で1つの塊として存在していた Django • • • admin.py に数⾏書くだけで、⼀覧‧編 集‧削除の画⾯が揃う モデルにフィールドを⾜せば、管理画⾯ にも⾃動で反映される 「管理画⾯の実装」という⼯程そのもの が、ほぼ消えた 35
  23. まとめ 振り返り appの思想 モデルの思想 Djangoは app を 再利⽤‧配布の単位に置いた Djangoはモデル定義を 上位レイヤまで貫通させた

    だから機能で切り、マイグレーションも同梱され、粒 度の基準は「配布できるか」になる だから null= と blank= が同居し、フォームも管理画⾯ もモデルから導出される 37
  24. まとめ 最初に読んでおきたいDjangoドキュメント 1 Applications 「appとは何か」「INSTALLED_APPSやAppConfigは何のためにあるか」が公式の⾔葉で書かれている。 docs.djangoproject.com/en/stable/ref/applications/ 2 Migrations 「マイグレーションはappに同梱して配布する設計」という内容の原⽂。dependenciesが⾃動⽣成される仕組みも書かれている。 docs.djangoproject.com/en/stable/topics/migrations/

    3 Model field reference ― null / blank 「⽂字列にnullを避ける」の理由と、unique制約の例外が書かれている。 docs.djangoproject.com/en/stable/ref/models/fields/#null 4 The Django admin site register以外のlist_display や search_fields など、実務で使う管理画⾯カスタマイズの⼀覧などが書かれている。 docs.djangoproject.com/en/stable/ref/contrib/admin/ 40