feat: add generic Value[T] base class for value objects

- Create Value[T] (frozen dataclass + ABC + Generic) in domain/framework/
- Migrate ClassifiedAdTitle and ClassifiedAdText to inherit Value[str]
- Standardize all value access via .value instead of type-specific fields
This commit is contained in:
2026-07-29 11:13:17 -04:00
parent 05229910cd
commit d345e522bd
9 changed files with 150 additions and 102 deletions

View File

@@ -28,23 +28,27 @@ from domain.marketplace.user_id import UserId
class ClassifiedAd(Entity):
id: ClassifiedAdId
_ownerId: UserId
title: ClassifiedAdTitle = field(default_factory=lambda: ClassifiedAdTitle(title=""))
text: ClassifiedAdText = field(default_factory=lambda: ClassifiedAdText(text=""))
title: ClassifiedAdTitle = field(
default_factory=lambda: ClassifiedAdTitle(value="")
)
text: ClassifiedAdText = field(default_factory=lambda: ClassifiedAdText(value=""))
price: Price = field(default_factory=lambda: Price(amount=Decimal("0.00")))
state: ClassifiedAdState = ClassifiedAdState.Inactive
_changes: tuple[DomainEvent, ...] = ()
def __post_init__(self):
if not self._changes:
object.__setattr__(self, '_changes', (
ClassifiedAdCreated(id=self.id.value, owner_id=self._ownerId.value),
))
object.__setattr__(
self,
"_changes",
(ClassifiedAdCreated(id=self.id.value, owner_id=self._ownerId.value),),
)
def _ensure_valid_state(self):
errors = []
if not self.title.title:
if not self.title.value:
errors.append("title")
if not self.text.text:
if not self.text.value:
errors.append("text")
if self.price.amount <= 0:
errors.append("price")
@@ -57,9 +61,9 @@ class ClassifiedAd(Entity):
def when(self, event: DomainEvent) -> Self:
match event:
case ClassifiedAdTitleChanged():
return replace(self, title=ClassifiedAdTitle(title=event.title))
return replace(self, title=ClassifiedAdTitle(value=event.title))
case ClassifiedAdTextUpdated():
return replace(self, text=ClassifiedAdText(text=event.ad_text))
return replace(self, text=ClassifiedAdText(value=event.ad_text))
case ClassifiedAdPriceUpdated():
return replace(self, price=Price(amount=event.price))
case ClassifiedAdSentForReview():
@@ -70,10 +74,12 @@ class ClassifiedAd(Entity):
return self.apply(ClassifiedAdSentForReview(id=self.id.value))
def set_title(self, title: ClassifiedAdTitle) -> ClassifiedAd:
return self.apply(ClassifiedAdTitleChanged(id=self.id.value, title=title.title))
return self.apply(ClassifiedAdTitleChanged(id=self.id.value, title=title.value))
def update_text(self, text: ClassifiedAdText) -> ClassifiedAd:
return self.apply(ClassifiedAdTextUpdated(id=self.id.value, ad_text=text.text))
return self.apply(ClassifiedAdTextUpdated(id=self.id.value, ad_text=text.value))
def update_price(self, price: Price) -> ClassifiedAd:
return self.apply(ClassifiedAdPriceUpdated(id=self.id.value, price=price.amount))
return self.apply(
ClassifiedAdPriceUpdated(id=self.id.value, price=price.amount)
)

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass
from domain.framework.value import Value
@dataclass(frozen=True)
class ClassifiedAdText:
text: str
class ClassifiedAdText(Value[str]):
pass

View File

@@ -1,10 +1,12 @@
from dataclasses import dataclass
from domain.framework.value import Value
@dataclass(frozen=True)
class ClassifiedAdTitle:
title: str
class ClassifiedAdTitle(Value[str]):
def __post_init__(self):
if len(self.title) > 100:
raise ValueError(f"Title cannot be longer than 100 characters: {self.title}")
if len(self.value) > 100:
raise ValueError(
f"Title cannot be longer than 100 characters: {self.value}"
)