Repository 设计模式主要是要分离商业逻辑与资料存取的逻辑,希望开发者专注在商业逻辑的设计,不必担心如何与资料库介接。
图一. DDD 分层
另外,领域驱动设计(DDD)强调领域物件(Domain Object)的重要性,资料库的存取逻辑应在领域物件类别内实践,微软的DDD文件指引主张一个Aggregate应该定义一个Repository,个别管理Aggregate内所有的实体(Entity),架构如下图。
图二. Aggregate 的 Repository 设计模式,图片来源:微软的DDD文件指引
上图的Data Tier对应ORM模型,透过模型物件存取资料库,而Repository就架在这一层之上,进行相关的 Domain Object 操作,一个Domain Object通常会包含多个资料表的操作。例如输入一张订单,至少包括两个资料表--表头(Order)、表身(Order details),如下图,表头包括客户名称、送货地址、订单日期、交货日期等,表身包括多笔资料,每一笔含商品名称、单价、数量、折扣等。
图三. 订单样本,图片来源:手写统一发票计算机 - 工具邦
以下我们就以Django套件来实践Repository设计模式。
Django 安装非常简单,执行下列指令:
pip install django
测试专案可至这里下载,专案目录为test1,资料库为db.sqlite3,预设为SQLite资料库。
Django要求每个资料表都要建立一个对应的类别,例如客户资料表:
from django.db import models
class Customer(models.Model):
customer_id = models.CharField(max_length=20)
customer_name = models.CharField(max_length=100)
contact = models.CharField(max_length=30, blank=True, null=True)
address = models.CharField(max_length=120, blank=True, null=True)
每个应用系统大概都会有数十个,乃至数百个资料表,若要赋予每个资料表『新增/查询/更正/删除(CRUD)』功能,如果每个类别分别撰写,不仅费时费工,而且不易维护,因此,我们可以应用Repository设计模式,完成上述的功能,程序码如下,档名为repository.py:
class Repository:
# 初始化,指定类别名称
def __init__(self, class1):
self.class1 = class1
# 单笔新增
def create(self, **dict1):
obj = self.class1()
for key in dict1.keys():
setattr(obj, key, dict1[key])
obj.save()
return obj
# 多笔新增
def bulk_create(self, objs):
return self.class1.objects.bulk_create(objs)
# 存档(新增或更新)
def save(self, obj):
return obj.save()
# 删除
def delete(self, pk):
return self.class1.objects.filter(pk=pk).delete()
# 删除2
def delete_object(self, obj):
return obj.delete()
# 单笔查询
def get_one(self, pk):
return self.class1.objects.get(pk=pk)
# 多笔查询
def get_many(self, **filter):
return self.class1.objects.filter(**filter)
使用 Django shell 测试每一个功能,先在专案目录test1,启动 shell:
python manage.py shell
from sales.models import *
from repository import *
# 指定类别名称
repo = Repository(Customer)
# 单笔查询,query by pk
cust = repo.get_one(2)
print(cust.customer_name)
顺利取得 pk=2 的资料列,显示customer_00002。pk为资料表主键(Primary Key),为自动给号。
查询customer_name含0000,而且id>=5。
obj_list = repo.get_many(customer_name__contains='0000', id__gte=5)
for obj in obj_list:
print(obj.customer_name)
显示5笔资料。
customer_00005
customer_00006
customer_00007
customer_00008
customer_00009
i=11
obj = Customer()
obj.customer_id=f'{i:05d}'
obj.customer_name=f'customer_{i:05d}'
obj.contact=f'contact_{i:05d}'
obj.address=f'address_{i:05d}'
repo.save(obj)
print(obj.id)
显示物件的pk=11。
repo.delete(11)
i=12
obj=repo.create(customer_id=f'{i:05d}',
customer_name=f'customer_{i:05d}',
contact=f'contact_{i:05d}',
address=f'address_{i:05d}',
)
print(obj.id)
显示物件的pk=12。
repo.delete_object(obj)
obj = repo.get_one(1)
obj.customer_id=f'XXX'
repo.save(obj)
pk=1的customer_id被改为'XXX',可再次查询求证。
一次新增十笔资料。
obj_list = []
for i in range(11, 21):
obj = Customer()
obj.customer_id=f'{i:05d}'
obj.customer_name=f'customer_{i:05d}'
obj.contact=f'contact_{i:05d}'
obj.address=f'address_{i:05d}'
obj_list.append(obj)
repo.bulk_create(obj_list)
为简化说明,repository.py 并未作例外控制及检查,读者可自行添加,例如添加一笔资料前先检查资料是否重覆。
一个Domain Object通常会包含多个资料表的操作。例如输入一张订单,至少包括两个资料表 -- 表头(Order)、表身(Order_detail),必须作好交易管理(Transaction),Django 提供很好的机制,可参考官网说明。
建立测试函数如下,可新增一笔订单,存档名称为 repository_do.py:
from sales.models import *
from repository import *
from django.db import transaction
# order save
@transaction.atomic
def create_order(cust):
repo = Repository(Order)
ord=repo.create(order_id='so_0001',
customer_id=cust,
order_date='20210801',
required_date='20210901',
)
# 多笔新增
repo_detail = Repository(Order_Detail)
repo_product = Repository(Product)
obj_list = []
for i in range(1, 4):
obj = Order_Detail()
obj.order_id=ord
obj.product_id=repo_product.get_one(i)
obj.unit_price=100
obj.quantity=5
obj.discount=0
obj_list.append(obj)
repo_detail.bulk_create(obj_list)
其中 @transaction.atomic 可以控制交易,如果 create_order() 内有任何错误,系统会自动 rollback,全部资料均不会写入资料库。
测试程序码如下:
from sales.models import *
from repository import *
from repository_do import *
# 取得客户物件
repo = Repository(Customer)
cust = repo.get_one(2)
# 新增一笔订单,含表头(Order)、表身(Order_detail)
create_order(cust)
检查资料库,sales_order、sales_order_detail 确实都写入资料了。
Repository 设计模式结合 ORM,可以大幅提高开发的速度,笔者曾经开发一个购物平台的前/後台,只花了两个月的时间,而且案主也非常满意,再结合 DDD 的设计概念,就更给力了。
<<: 【课程推荐】2021/11/6-11/7软件需求塑模与需求规格文件撰写实务班
>>: 用 Python 畅玩 Line bot - 21:LIFF(一)
前言 昨天已经模拟出改价了,现在更进阶,使用小台的现价来改价。 参考网站:Futures 本日程序码...
本篇主要论述源自於 XState 作者本人在 stackoverflow 的解释,并辅以一点我的补充...
在开发时,常常需要多个指令同时运作(例如一个启动前端专案、一个启动後端专案),因此会需要同时开启多个...
Instantiate实例化,用於将物件生成至场景中。(参考unity手册) 适合用在复制一样的物件...
已经先有测试资料了 来试试看删除文件的方法 doc_info/views.py 一样使用修饰器来验证...