Translators¶
The Translator pattern (often also known as a Mapper or Converter) is a design pattern used to transform data from one object representation (Source) to another (Target). This is particularly useful when dealing with different layers of an application (e.g., DTOs to Domain Models, Domain Models to View Models) or when integrating with external systems that have different data structures.
What is a Translator?¶
A Translator object typically has one core method, translate(source)
, which takes an object of the source type and returns a new object of the target type, performing the necessary data mapping and transformation logic.
Castlecraft Engineer provides an abstract base class Translator[Source, Target]
that your concrete translators can inherit from. Source
and Target
are generic type parameters representing the types of the objects involved in the translation.
Key Benefits¶
- Encapsulation of Transformation Logic: The rules for converting data from one format to another are encapsulated within specific translator classes. This keeps your core business logic clean from mapping concerns.
- Decoupling & Anti-Corruption: It decouples the source and target structures. Changes in one structure might only require updates to the translator, not necessarily to the code that uses these structures. This is fundamental when implementing an Anti-Corruption Layer (ACL) to protect your domain model from the complexities or direct dependencies of external systems or other bounded contexts.
- Reusability: Translators can be reused wherever the same type of transformation is needed.
- Testability: Each translator can be tested in isolation to ensure the mapping logic is correct.
- Clarity: Explicit translator classes make the data transformation process clear and maintainable.
The Translator
Abstraction¶
The castlecraft_engineer.abstractions.translator.Translator
class provides:
- An abstract method
translate(self, source: Source) -> Target
that must be implemented by concrete translators for synchronous translation. - A virtual method
translate_async(self, source: Source) -> Target
for asynchronous translation. By default, it calls the synchronoustranslate
method. Implementers should override this if their translation logic involves asynchronous operations (e.g., fetching additional data from a database or external service). - A
TranslationError
exception that can be raised by translators to indicate issues during the transformation process.
Example Usage¶
Let's say we have a UserDTO
(Data Transfer Object) coming from an API request and we want to translate it into a User
domain model.
from dataclasses import dataclass
from castlecraft_engineer.abstractions.translator import Translator, TranslationError
# Source Object
@dataclass
class UserDTO:
user_id: str
full_name: str
email: str
is_active_user: bool
# Target Object (Domain Model)
@dataclass
class User:
id: str
name: str
email_address: str
active: bool
# Concrete Translator
class UserDtoToDomainTranslator(Translator[UserDTO, User]):
def translate(self, source: UserDTO) -> User:
if not source.email:
raise TranslationError("UserDTO must have an email for translation.")
# Example transformation logic
name_parts = source.full_name.split(" ", 1)
first_name = name_parts[0]
# Potentially more complex logic here
return User(
id=source.user_id,
name=first_name, # Or full_name, depending on domain needs
email_address=source.email.lower(),
active=source.is_active_user
)
# Using the translator
dto = UserDTO(user_id="usr_123", full_name="Jane Doe", email="Jane.Doe@Example.com", is_active_user=True)
translator = UserDtoToDomainTranslator()
try:
user_domain_model = translator.translate(dto)
print(f"Translated User: {user_domain_model}")
# Output: Translated User: User(id='usr_123', name='Jane', email_address='jane.doe@example.com', active=True)
except TranslationError as e:
print(f"Translation failed: {e}")
# Example of an invalid DTO
invalid_dto = UserDTO(user_id="usr_456", full_name="No Email", email="", is_active_user=False)
try:
translator.translate(invalid_dto)
except TranslationError as e:
print(f"Translation failed: {e}")
# Output: Translation failed: UserDTO must have an email for translation.
Use Cases¶
- Mapping Data Transfer Objects (DTOs) to Domain Models and vice-versa.
- Transforming Domain Models into View Models or API responses.
- Converting data from external API responses into internal application models.
- Adapting data between different versions of an object.
- Implementing an Anti-Corruption Layer (ACL) by translating data between your core domain and external systems or legacy components.
- Preparing data for use in other systems or components, such as authorization services or reporting tools.