Python DDD项目中MongoEngine如何保留自定义字段类型?
解决MongoEngine读取时保留DDD自定义值对象类型的问题
你遇到的核心问题是MongoEngine默认会将数据库存储的基础类型(UUID、字符串)直接映射为Python原生类型,而没有自动转换回你的DDD自定义值对象(ExperimentId、ExperimentName)。要解决这个问题,需要给自定义值对象实现对应的MongoEngine字段类,接管序列化和反序列化逻辑。
1. 实现自定义值对象与对应MongoEngine字段
先确保你的值对象是符合DDD特性的封装类,再为其实现专属的MongoEngine字段,处理类型转换逻辑:
ExperimentId 值对象与字段
from uuid import UUID from mongoengine.fields import UUIDField class ExperimentId: def __init__(self, value: UUID): if not isinstance(value, UUID): raise ValueError("ExperimentId必须是UUID类型") self.value = value @classmethod def from_string(cls, uuid_str: str): return cls(UUID(uuid_str)) def __eq__(self, other): return isinstance(other, ExperimentId) and self.value == other.value def __str__(self): return str(self.value) class ExperimentIdField(UUIDField): def to_python(self, value): # 从数据库读取时,将UUID转换为ExperimentId if value is None: return None if isinstance(value, ExperimentId): return value uuid_val = super().to_python(value) return ExperimentId(uuid_val) def to_db(self, value): # 写入数据库时,将ExperimentId转为UUID存储 if isinstance(value, ExperimentId): return value.value return super().to_db(value) def prepare_query_value(self, op, value): # 支持用ExperimentId对象直接查询 if isinstance(value, ExperimentId): value = value.value return super().prepare_query_value(op, value)
ExperimentName 值对象与字段
from mongoengine.fields import StringField class ExperimentName: def __init__(self, value: str): if not isinstance(value, str) or not value.strip(): raise ValueError("ExperimentName不能为空字符串") self.value = value.strip() def __eq__(self, other): return isinstance(other, ExperimentName) and self.value == other.value def __str__(self): return self.value class ExperimentNameField(StringField): def to_python(self, value): # 从数据库读取时,将字符串转换为ExperimentName if value is None: return None if isinstance(value, ExperimentName): return value str_val = super().to_python(value) return ExperimentName(str_val) def to_db(self, value): # 写入数据库时,将ExperimentName转为字符串存储 if isinstance(value, ExperimentName): return value.value return super().to_db(value) def prepare_query_value(self, op, value): # 支持用ExperimentName对象直接查询 if isinstance(value, ExperimentName): value = value.value return super().prepare_query_value(op, value)
2. 修改Experiment类使用自定义字段
将原来的ExperimentId.Field和ExperimentName.Field替换为我们实现的专属字段:
from mongoengine import Document, DateTimeField from datetime import datetime from your_module import ExperimentIdField, ExperimentNameField, Entity, NoLabsException, ErrorCodes class Experiment(Document, Entity): id: ExperimentId = ExperimentIdField(db_field='_id', primary_key=True, required=True) name: ExperimentName = ExperimentNameField(required=True) created_at: datetime = DateTimeField() def __init__(self, id: ExperimentId, name: ExperimentName): if not id: raise NoLabsException('Experiment id is empty', ErrorCodes.invalid_experiment_id) if not name: raise NoLabsException('Experiment name is empty', ErrorCodes.invalid_experiment_name) created_at = datetime.now(tz=datetime.timezone.utc) super().__init__(id=id, name=name, created_at=created_at) def set_name(self, name: ExperimentName): if not name: raise NoLabsException('Name cannot be empty', ErrorCodes.invalid_experiment_name) self.name = name
原理说明
MongoEngine的字段通过三个核心方法处理类型转换:
to_python:将数据库返回的原始值转换为目标Python对象(即你的自定义值对象)to_db:将Python对象转换为数据库可存储的原始类型prepare_query_value:确保使用自定义值对象查询时,能正确转换为数据库可识别的类型
修改后,从数据库读取Experiment实例时,id会自动保持ExperimentId类型,name保持ExperimentName类型,同时不影响数据的存储和查询逻辑。
内容的提问来源于stack exchange,提问作者JaktensTid
相关产品推荐
相关产品推荐

