[英]Classical mapping in MongoEngine
我是 MongoEngine 的新手,看起來我們需要從mongoengine
創建類Document
的子類來建模我們的數據庫。 我在這里有點擔心,因為這違反了 SOLID 原則中的依賴倒置。 因此,如果我以后需要使用另一個數據庫,我將不得不更改我不應該做的領域模型類。
SQLAlchemy 通過提供漂亮的經典映射克服了這個問題。 使用它,依賴於數據庫的代碼與我的域模型分離,所以我真的不需要擔心數據庫提供者,如果我需要更改我的數據庫,我可以輕松地抽象出細節。
MongoDB是否有類似的東西,最好是在MongoEngine中?
Pymongo 的官方文檔提供了現有 ORM/ODM 和框架的列表,但據我所知,它們都實現了Active Record 模式(就像 django ORM 一樣),正如你所說,這違反了 SOLID 原則,但對於許多簡單的用例來說已經足夠了.
受 SQLAlchemy 啟發的 MongoAlchemy 使用會話的概念,因此它可能更接近您正在尋找的內容,但不再維護該項目。
如果我理解正確,您正在嘗試使用 mongoengine 將對象映射到文檔模式。
讓我們為用戶創建一個文檔類:
from mongoengine import Document, StringField
class UserDocument(Document):
username = StringField(required=True)
password = StringField(required=True)
email = StringField(required=True)
現在添加一個創建新用戶的類方法:
from mongoengine import disconnect, connect, Document, StringField
class UserDocument(Document):
username = StringField(required=True)
password = StringField(required=True)
email = StringField(required=True)
@classmethod
def new(cls):
data = UserDocument(username=cls.username, password=cls.password, email=cls.email)
connect('test_collection')
data.save()
disconnect('test_collection')
據我了解您的問題,您在此示例中的問題是 UserDocument 會意識到 mongoengine 因此違反了依賴倒置原則。 這可以通過子類來解決。
首先允許在 UserDocument 中繼承:
...
class UserDocument(Document):
meta = {'allow_inheritance': True}
username = StringField(required=True)
...
接下來我們構建孩子:
from user_document import UserDocument
# Maps object to schema
class User(UserDocument):
def __init__(self, *args, **values):
super().__init__(*args, **values)
接下來添加一個創建方法:
from user_document import UserDocument
# Maps object to schema
class User(UserDocument):
def __init__(self, *args, **values):
super().__init__(*args, **values)
def create(self, username, password, email):
self.username, self.password, self.email = username, password, email
User.new()
現在我們的 User 對象繼承了 UserDocument 字段。 可以直接訪問 UserDocument.new,也可以使用 User.new() 通過子級訪問。
from model import User
username, password, email = 'cool username', 'super secret password', 'mrcool@example.com'
User.create(User, username, password, email)
User 對象知道 UserDocument,而后者又依賴於 mongoengine。
如果我誤解或使用了不正確的詞匯來描述示例解決方案,我深表歉意。 我相對較新,自學成才,沒有朋友編寫代碼,這使得討論變得困難。
這個主題在CosmicPython/Architecture Patterns With Python的前 6 章中有介紹。 但是,在那些章節中,它使用 SQLAlchemy 和映射器。
這本書確實有一個部分,其中包含使用 ActiveRecord 樣式的其他 ORM 的示例——如 mongoengine——在附錄 D:帶有 Django 的存儲庫和工作單元模式。
首先定義模型。
請注意,如果沒有任何背景,下面的例子可能很難理解,所以如果下面的例子不清楚,我建議閱讀 CosmicPython 的前 6 章。
src/djangoproject/alloc/models.py
from django.db import models
from allocation.domain import model as domain_model
class Batch(models.Model):
reference = models.CharField(max_length=255)
sku = models.CharField(max_length=255)
qty = models.IntegerField()
eta = models.DateField(blank=True, null=True)
@staticmethod
def update_from_domain(batch: domain_model.Batch):
try:
b = Batch.objects.get(reference=batch.reference)
except Batch.DoesNotExist:
b = Batch(reference=batch.reference)
b.sku = batch.sku
b.qty = batch._purchased_quantity
b.eta = batch.eta
b.save()
b.allocation_set.set(
Allocation.from_domain(l, b)
for l in batch._allocations
)
def to_domain(self) -> domain_model.Batch:
b = domain_model.Batch(
ref=self.reference, sku=self.sku, qty=self.qty, eta=self.eta
)
b._allocations = set(
a.line.to_domain()
for a in self.allocation_set.all()
)
return b
class OrderLine(models.Model):
orderid = models.CharField(max_length=255)
sku = models.CharField(max_length=255)
qty = models.IntegerField()
def to_domain(self):
return domain_model.OrderLine(
orderid=self.orderid, sku=self.sku, qty=self.qty
)
@staticmethod
def from_domain(line):
l, _ = OrderLine.objects.get_or_create(
orderid=line.orderid, sku=line.sku, qty=line.qty
)
return l
class Allocation(models.Model):
batch = models.ForeignKey(Batch, on_delete=models.CASCADE)
line = models.ForeignKey(OrderLine, on_delete=models.CASCADE)
@staticmethod
def from_domain(domain_line, django_batch):
a, _ = Allocation.objects.get_or_create(
line=OrderLine.from_domain(domain_line),
batch=django_batch,
)
return a
然后在src/allocation/adapters/repository.py
中為存儲庫模式定義一個端口和適配器
# pylint: disable=no-member, no-self-use
from typing import Set
import abc
from allocation.domain import model
from djangoproject.alloc import models as django_models
class AbstractRepository(abc.ABC):
def __init__(self):
self.seen = set() # type: Set[model.Batch]
def add(self, batch: model.Batch):
self.seen.add(batch)
def get(self, reference) -> model.Batch:
p = self._get(reference)
if p:
self.seen.add(p)
return p
@abc.abstractmethod
def _get(self, reference):
raise NotImplementedError
class DjangoRepository(AbstractRepository):
def add(self, batch):
super().add(batch)
self.update(batch)
def update(self, batch):
django_models.Batch.update_from_domain(batch)
def _get(self, reference):
return (
django_models.Batch.objects.filter(reference=reference)
.first()
.to_domain()
)
def list(self):
return [b.to_domain() for b in django_models.Batch.objects.all()]
連同域模型src/allocation/domain/model.py
from __future__ import annotations
from dataclasses import dataclass
from datetime import date
from typing import Optional, List, Set
class OutOfStock(Exception):
pass
def allocate(line: OrderLine, batches: List[Batch]) -> str:
try:
batch = next(b for b in sorted(batches) if b.can_allocate(line))
batch.allocate(line)
return batch.reference
except StopIteration:
raise OutOfStock(f"Out of stock for sku {line.sku}")
@dataclass(unsafe_hash=True)
class OrderLine:
orderid: str
sku: str
qty: int
class Batch:
def __init__(self, ref: str, sku: str, qty: int, eta: Optional[date]):
self.reference = ref
self.sku = sku
self.eta = eta
self._purchased_quantity = qty
self._allocations = set() # type: Set[OrderLine]
def __repr__(self):
return f"<Batch {self.reference}>"
def __eq__(self, other):
if not isinstance(other, Batch):
return False
return other.reference == self.reference
def __hash__(self):
return hash(self.reference)
def __gt__(self, other):
if self.eta is None:
return False
if other.eta is None:
return True
return self.eta > other.eta
def allocate(self, line: OrderLine):
if self.can_allocate(line):
self._allocations.add(line)
def deallocate(self, line: OrderLine):
if line in self._allocations:
self._allocations.remove(line)
@property
def allocated_quantity(self) -> int:
return sum(line.qty for line in self._allocations)
@property
def available_quantity(self) -> int:
return self._purchased_quantity - self.allocated_quantity
def can_allocate(self, line: OrderLine) -> bool:
return self.sku == line.sku and self.available_quantity >= line.qty
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.