我們最近在產品中新增了一個類似於銀行賬戶的功能。 在開發過程中,我們遇到了一些textbook的問題,我認為這是一個很好的機會,可以在 Django 模型中演練一些模式。 這篇文章是按照我們通常處理新問題的順序寫的:
- 定義業務需求
- 寫一個初步的實現並定義模型
- 挑戰解決方案
- 不斷改進
銀行賬戶的業務需求
- 每個使用者只能有一個賬戶,或沒有
- 使用者可以存款並從賬戶中提取一定數量的錢
- 帳戶餘額不能是負數
- 使用者的賬戶餘額有一個最大額度
- 所有賬戶的總餘額不能超過一定數量
- 賬戶上的每一個操作都必須有記錄
- 使用者可以從移動應用或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',
)
複製程式碼
讓我們來分析一下:
- 我們使用兩個唯一的識別符號:一個私有識別符號,它是一個自動生成的數字(id),和一個公共 id (uid)。 保持自增主鍵的隱私是一個好主意,它們會暴露我們資料的重要資訊,比如我們有多少賬戶,我們不希望這樣。
- 我們為使用者使用 OneToOneField。 這就像一個 ForeignKey,但是有一個唯一的約束。 這確保了使用者不能有一個以上帳戶
- 我們設定 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.',
)
複製程式碼
在這兒插入另一篇文章,關於微信紅包設計的