データモデルとドメインモデルの違いについて質問しているようです。後者は、エンドユーザーが認識するビジネスロジックとエンティティを見つけることができる場所です。前者は実際にデータを保存する場所です。
さらに、私はあなたの質問の3番目の部分を次のように解釈しました:これらのモデルを分離しておくことができないことに気づく方法。
これらは2つの非常に異なる概念であり、それらを分離しておくことは常に困難です。ただし、この目的で使用できる一般的なパターンとツールがいくつかあります。
ドメインモデルについて
最初に認識する必要があるのは、ドメインモデルは実際にはデータに関するものではないということです。「このユーザーをアクティブにする」、「このユーザーを非アクティブにする」、「現在アクティブになっているユーザーは?」、「このユーザーの名前は何ですか?」などのアクションと質問についてです。古典的な用語では、それはクエリとコマンドについてです。
コマンドで考える
まず、例の「このユーザーをアクティブにする」と「このユーザーを非アクティブにする」のコマンドを見てみましょう。コマンドの良いところは、与えられたときのシナリオで簡単に表現できることです。
与えられた非アクティブユーザ管理者は、このユーザがアクティブその後、ユーザがアクティブになると確認メールがユーザに送信されると、エントリは、システムログに追加される
(等など)
このようなシナリオは、インフラストラクチャのさまざまな部分が単一のコマンドによってどのように影響を受けるかを確認するのに役立ちます。この場合、データベース(ある種の「アクティブ」フラグ)、メールサーバー、システムログなどです。
このようなシナリオは、テスト駆動開発環境の設定にも役立ちます。
最後に、コマンドを考えることは、タスク指向のアプリケーションを作成するのに非常に役立ちます。ユーザーはこれに感謝します:-)
コマンドの表現
Djangoはコマンドを表現する2つの簡単な方法を提供します。どちらも有効なオプションであり、2つのアプローチを組み合わせるのは珍しいことではありません。
サービス層
サービスモジュールがすでにされています@Heddeで説明します。ここでは、個別のモジュールを定義し、各コマンドは関数として表されます。
services.py
def activate_user(user_id):
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
フォームを使用する
もう1つの方法は、各コマンドにDjangoフォームを使用することです。私はこのアプローチを好みます。なぜなら、それは複数の密接に関連する側面を組み合わせるからです:
- コマンドの実行(それは何をしますか?)
- コマンドパラメータの検証(これは可能ですか?)
- コマンドの表示(どうすればよいですか?)
forms.py
class ActivateUserForm(forms.Form):
user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
# the username select widget is not a standard Django widget, I just made it up
def clean_user_id(self):
user_id = self.cleaned_data['user_id']
if User.objects.get(pk=user_id).active:
raise ValidationError("This user cannot be activated")
# you can also check authorizations etc.
return user_id
def execute(self):
"""
This is not a standard method in the forms API; it is intended to replace the
'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern.
"""
user_id = self.cleaned_data['user_id']
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
クエリで考える
あなたの例にはクエリが含まれていなかったので、いくつかの便利なクエリを自由に作成しました。私は「質問」という用語を使用することを好みますが、クエリは古典的な用語です。興味深いクエリは、「このユーザーの名前は何ですか?」、「このユーザーはログインできますか?」、「非アクティブ化されたユーザーのリストを表示してください」、および「非アクティブ化されたユーザーの地理的分布は何ですか?」です。
これらのクエリへの回答に着手する前に、常に2つの質問を自問する必要があります。これは、テンプレート専用のプレゼンテーションクエリ、および/またはコマンドの実行に関連付けられたビジネスロジッククエリ、および/またはレポートクエリですか。
プレゼンテーションのクエリは、単にユーザーインターフェイスを改善するために行われます。ビジネスロジッククエリへの回答は、コマンドの実行に直接影響します。レポートクエリは分析のみを目的としており、時間の制約が緩くなっています。これらのカテゴリは相互に排他的ではありません。
もう1つの質問は、「答えを完全に制御できるか?」です。たとえば、(このコンテキストでは)ユーザーの名前を照会する場合、外部APIに依存しているため、結果を制御することはできません。
クエリを作成する
Djangoの最も基本的なクエリは、Managerオブジェクトの使用です。
User.objects.filter(active=True)
もちろん、これは、データが実際にデータモデルで表現されている場合にのみ機能します。これは常にそうであるとは限りません。そのような場合は、以下のオプションを検討できます。
カスタムタグとフィルター
最初の代替手段は、単に表示用のクエリであるカスタムタグとテンプレートフィルターに役立ちます。
template.html
<h1>Welcome, {{ user|friendly_name }}</h1>
template_tags.py
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
クエリメソッド
クエリが単なるプレゼンテーションではない場合は、services.py(使用している場合)にクエリを追加するか、querys.pyを導入できます。モジュールをます。
querys.py
def inactive_users():
return User.objects.filter(active=False)
def users_called_publysher():
for user in User.objects.all():
if remote_api.get_cached_name(user.id) == "publysher":
yield user
プロキシモデル
プロキシモデルは、ビジネスロジックおよびレポートのコンテキストで非常に役立ちます。基本的に、モデルの拡張サブセットを定義します。Managerの基本QuerySetをオーバーライドするには、Manager.get_queryset()
メソッド。
models.py
class InactiveUserManager(models.Manager):
def get_queryset(self):
query_set = super(InactiveUserManager, self).get_queryset()
return query_set.filter(active=False)
class InactiveUser(User):
"""
>>> for user in InactiveUser.objects.all():
… assert user.active is False
"""
objects = InactiveUserManager()
class Meta:
proxy = True
クエリモデル
本質的に複雑であるがかなり頻繁に実行されるクエリの場合、クエリモデルの可能性があります。クエリモデルは、1つのクエリに関連するデータが別のモデルに格納される非正規化の形式です。もちろん、トリックは、非正規化モデルをプライマリモデルと同期させることです。クエリモデルは、変更が完全に自分の管理下にある場合にのみ使用できます。
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
最初のオプションは、コマンドでこれらのモデルを更新することです。これは、これらのモデルが1つまたは2つのコマンドによってのみ変更される場合に非常に役立ちます。
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
より良いオプションは、カスタム信号を使用することです。これらの信号はもちろん、コマンドによって発行されます。信号には、複数のクエリモデルを元のモデルと同期させることができるという利点があります。さらに、Celeryまたは同様のフレームワークを使用して、信号処理をバックグラウンドタスクにオフロードできます。
signal.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
models.py
class InactiveUserDistribution(models.Model):
# see above
@receiver(user_activated)
def on_user_activated(sender, **kwargs):
user = kwargs['user']
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
清潔に保つ
このアプローチを使用すると、コードがクリーンなままであるかどうかを判断するのが途方もなく簡単になります。次のガイドラインに従ってください。
- 私のモデルには、データベースの状態を管理するだけではないメソッドが含まれていますか?コマンドを抽出する必要があります。
- モデルに、データベースフィールドにマップされないプロパティが含まれていますか?クエリを抽出する必要があります。
- モデルがデータベースではないインフラストラクチャ(メールなど)を参照していますか?コマンドを抽出する必要があります。
ビューにも同じことが言えます(ビューが同じ問題に悩まされることが多いため)。
- 私のビューはデータベースモデルを積極的に管理していますか?コマンドを抽出する必要があります。
いくつかの参考文献
Djangoドキュメント:プロキシモデル
Djangoドキュメント:シグナル
アーキテクチャ:ドメイン駆動設計