Skip to content

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 synchronous translate 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.