翻譯:Bullet Proofing Django Models 待更新

青穗黃發表於2018-10-26

我們最近在產品中新增了一個類似於銀行賬戶的功能。 在開發過程中,我們遇到了一些textbook的問題,我認為這是一個很好的機會,可以在 Django 模型中演練一些模式。 這篇文章是按照我們通常處理新問題的順序寫的:

  1. 定義業務需求
  2. 寫一個初步的實現並定義模型
  3. 挑戰解決方案
  4. 不斷改進

銀行賬戶的業務需求

  • 每個使用者只能有一個賬戶,或沒有
  • 使用者可以存款並從賬戶中提取一定數量的錢
  • 帳戶餘額不能是負數
  • 使用者的賬戶餘額有一個最大額度
  • 所有賬戶的總餘額不能超過一定數量
  • 賬戶上的每一個操作都必須有記錄
  • 使用者可以從移動應用或web介面的個人管理頁面執行操作

開始定義模型

帳戶模型

# models.py
import uuid
from django.conf import settings
from django.db import models
class Account(models.Model):
    class Meta:
        verbose_name = 'Account'
        verbose_name_plural = 'Accounts'
    MAX_TOTAL_BALANCES = 10000000
    MAX_BALANCE = 10000
    MIN_BALANCE = 0
    MAX_DEPOSIT = 1000
    MIN_DEPOSIT = 1
    MAX_WITHDRAW = 1000
    MIN_WITHDRAW = 1
    id = models.AutoField(
        primary_key=True,
    )
    uid = models.UUIDField(
        unique=True,
        editable=False,
        default=uuid.uuid4,
        verbose_name='Public identifier',
    )
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
    )
    created = models.DateTimeField(
        blank=True,
    )
    modified = models.DateTimeField(
        blank=True,
    )
    balance = models.PositiveIntegerField(
        verbose_name='Current balance',
    )
複製程式碼

讓我們來分析一下:

  1. 我們使用兩個唯一的識別符號:一個私有識別符號,它是一個自動生成的數字(id),和一個公共 id (uid)。 保持自增主鍵的隱私是一個好主意,它們會暴露我們資料的重要資訊,比如我們有多少賬戶,我們不希望這樣。
  2. 我們為使用者使用 OneToOneField。 這就像一個 ForeignKey,但是有一個唯一的約束。 這確保了使用者不能有一個以上帳戶
  3. 我們設定 on_delete=models.PROTECT。 從Django 2.0開始,這個引數將是強制性的。 預設值為CASCADE,當使用者被刪除時,相關的帳戶也會被刪除。在我們的情況下,這樣做沒有意義,想象一下當你關閉一個賬戶時,銀行會"刪除你的錢"。 設定成 on_delete=models.PROTECT時,當試圖刪除一個關聯user時時,會引發 IntegrityError。

帳戶操作模型

現在我們有了一個帳戶模型,我們可以建立一個模型來記錄對帳戶的操作:

# models.py
class Action(models.Model):
    class Meta:
        verbose_name = 'Account Action'
        verbose_name_plural = 'Account Actions'
    ACTION_TYPE_CREATED = 'CREATED'
    ACTION_TYPE_DEPOSITED = 'DEPOSITED'
    ACTION_TYPE_WITHDRAWN = 'WITHDRAWN'
    ACTION_TYPE_CHOICES = (
        (ACTION_TYPE_CREATED, 'Created'),
        (ACTION_TYPE_DEPOSITED, 'Deposited'),
        (ACTION_TYPE_WITHDRAWN, 'Withdrawn'),
    )
    REFERENCE_TYPE_BANK_TRANSFER = 'BANK_TRANSFER'
    REFERENCE_TYPE_CHECK = 'CHECK'
    REFERENCE_TYPE_CASH = 'CASH'
    REFERENCE_TYPE_NONE = 'NONE'
    REFERENCE_TYPE_CHOICES = (
        (REFERENCE_TYPE_BANK_TRANSFER, 'Bank Transfer'),
        (REFERENCE_TYPE_CHECK, 'Check'),
        (REFERENCE_TYPE_CASH, 'Cash'),
        (REFERENCE_TYPE_NONE, 'None'),
    )
    id = models.AutoField(
        primary_key=True,
    )
    user_friendly_id = models.CharField(
        unique=True,
        editable=False,
        max_length=30,
    )
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.PROTECT,
        help_text=’User who performed the action.’,
    )
    created = models.DateTimeField(
        blank=True,
    )
    account = models.ForeignKey(
        Account,
    )
    type = models.CharField(
        max_length=30,
        choices=ACTION_TYPE_CHOICES,
    )
    delta = models.IntegerField(
        help_text='Balance delta.',
    )
    reference = models.TextField(
        blank=True,
    )
    reference_type = models.CharField(
        max_length=30,
        choices=REFERENCE_TYPE_CHOICES,
        default=REFERENCE_TYPE_NONE,
    )
    comment = models.TextField(
        blank=True,
    )
    # Fields used solely for debugging purposes.
    
    debug_balance = models.IntegerField(
        help_text='Balance after the action.',
    )
複製程式碼

原文

在這兒插入另一篇文章,關於微信紅包設計

相關文章