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

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

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

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