Skip to content

Examples

Small, architecture-neutral usage snippets

This page collects small, focused examples that show how to use ForgingBlocks concepts in isolation.


Quick summary

This page collects small, focused, architecture-neutral examples showing how to use ForgingBlocks concepts in isolation. Each example is self-contained and assumes no particular project structure.

Examples included:
1. Validation with Result — Input parsing with explicit success/failure
2. Simple domain-like type — Building types that don't rely on infrastructure
3. Using a port and adapter — Defining boundaries with Port and implementing adapters
4. Modeling a value with ValueObject — Immutable, value-based equality types
5. Composing errors with structured types — Structured error modeling

These are usage snippets, not templates — adapt them to your context.


1. Validation with Result

from dataclasses import dataclass
from forging_blocks.foundation import Result, Ok, Err


@dataclass(frozen=True)
class RegisterUserInput:
    email: str
    name: str


def validate_registration(data: RegisterUserInput) -> Result[RegisterUserInput, str]:
    if "@" not in data.email:
        return Err("invalid email")
    if not data.name.strip():
        return Err("name required")
    return Ok(data)

Usage:

incoming = RegisterUserInput(email="user@example.com", name="Alice")
result = validate_registration(incoming)

match result:
    case Ok(valid):
        print(f"Ready to register: {valid}")
    case Err(error):
        print(f"Validation error: {error}")

2. Simple domain-like type (Entity)

from typing import Self

from forging_blocks.domain import Entity


class Task(Entity[int]):
    def __init__(self, id: int, title: str, completed: bool = False) -> None:
        super().__init__(id)
        self._title = title
        self._completed = completed

    @property
    def title(self) -> str:
        return self._title

    @property
    def completed(self) -> bool:
        return self._completed

    def complete(self) -> Self:
        self._completed = True
        return self

Entity uses selective freezing via @auto_freeze(attrs=["_id"]) — the identity field (_id) is frozen after __init__, while other attributes remain mutable. This ensures the entity's identity never changes, while its state can evolve. See Domain > Entities for why identity matters, and Foundation > Auto-freeze for the mechanism.


3. Using a port and adapter

from forging_blocks.foundation import Err, Ok, Port, Result


class EmailSender(Port):
    def send(self, to: str, subject: str, body: str) -> Result[None, str]:
        ...

A console-based implementation:

class ConsoleEmailSender:
    def send(self, to: str, subject: str, body: str) -> Result[None, str]:
        print(f"To: {to}\nSubject: {subject}\n\n{body}")
        return Ok(None)

A small function using the port:

def send_reset_email(sender: EmailSender, email: str) -> Result[None, str]:
    if "@" not in email:
        return Err("invalid email")
    body = "Click here to reset your password."
    return sender.send(to=email, subject="Reset your password", body=body)

The design is:

  • clear to read,
  • easy to test with a fake EmailSender,
  • independent of any particular mail provider or framework.

4. Modeling a value with ValueObject

from forging_blocks.foundation.value_object import ValueObject


class Email(ValueObject[str]):
    def __init__(self, value: str) -> None:
        super().__init__()
        if "@" not in value:
            raise ValueError("Invalid email")
        self._value = value

    @property
    def value(self) -> str:
        return self._value

    @property
    def _equality_components(self) -> tuple[str, ...]:
        return (self._value,)

ValueObject uses automatic freezing — subclasses need no @auto_freeze
decorator or _freeze() call. Two Email instances with the same value are
considered equal and can be used interchangeably as dictionary keys or set
members. Attempting to mutate one after construction raises a
CantModifyImmutableAttributeError.


5. Composing errors with structured types

from forging_blocks.foundation import (
    CombinedValidationErrors,
    ErrorMessage,
    FieldReference,
    ValidationError,
    ValidationFieldErrors,
)


def validate_email(value: str) -> list[ValidationError]:
    errors: list[ValidationError] = []
    if "@" not in value:
        errors.append(
            ValidationError(ErrorMessage("email must contain '@'"))
        )
    return errors


def validate_name(value: str) -> list[ValidationError]:
    errors: list[ValidationError] = []
    if not value.strip():
        errors.append(
            ValidationError(ErrorMessage("name must not be empty"))
        )
    return errors


def validate_user(email: str, name: str) -> list[ValidationFieldErrors]:
    field_errors: list[ValidationFieldErrors] = []

    email_errors = validate_email(email)
    if email_errors:
        field_errors.append(
            ValidationFieldErrors(FieldReference("email"), email_errors)
        )

    name_errors = validate_name(name)
    if name_errors:
        field_errors.append(
            ValidationFieldErrors(FieldReference("name"), name_errors)
        )

    return field_errors

Usage:

issues = validate_user("no-at-symbol", "")

if issues:
    raise CombinedValidationErrors(issues)

The error model is intentionally architecture-neutral.
The same types can be raised as exceptions, returned inside an Err, or
aggregated for reporting.


6. What to explore next

Once you are comfortable with these examples, you can:

  • read Principles to understand why the toolkit is structured this way,
  • map examples into blocks using Recommended Blocks Structure,
  • and explore architectural mappings in the Architectural Styles section if you want to see how these ideas can appear inside well-known styles.