Foundation¶
Foundational abstractions and contracts¶
The Foundation block provides low-level, reusable abstractions that are shared
across the entire system.
It contains no domain logic, no application orchestration, and
no infrastructure concerns.
Foundation exists to define stable contracts and primitives on top of which
all other blocks are built.
Purpose¶
- Supply primitive abstractions (
Result,Port,Mapper,Identified,ValueObject,Message,Command,Event,Query, plus error and meta utilities). - Enable explicit, intention-revealing boundaries.
- Support structured and predictable error handling.
- Provide a stable base reused by all other blocks.
Dependency position¶
flowchart TD
D[Domain] -->|depends on| F[Foundation]
A[Application] -->|depends on| F
A -->|depends on| D
I[Infrastructure] -->|depends on| A
I -->|depends on| F
P[Presentation] -->|depends on| A
P -->|depends on| F
Foundation depends on nothing.
All other blocks may depend on Foundation.
Core concepts¶
Result¶
Result models the explicit outcome of an operation.
It is used when failure is part of normal behavior, such as:
- validation
- parsing
- rule evaluation
- boundary checks
A Result represents either success (Ok) or failure (Err), without relying on
exceptions for control flow.
Result is about flow, not about error representation.
In the codebase, Result is a Protocol that describes the shared shape of both
variants, while Ok and Err are the concrete implementations you actually
construct and return.
Result supports Python's structural pattern matching through __match_args__,
so you can write match statements directly against Ok(...) and Err(...)
patterns.
Common operations on a Result include:
is_okandis_errfor branching on the variant.valueanderrorfor unwrapping the carried payload.mapfor transforming the success value while passing errors through.map_errorfor transforming the error while passing success through.flat_mapfor chaining operations that themselves return aResult.get_value_or(default)for supplying a fallback when the result is an error.get_value_or_else(fn)for computing a fallback from the carried error.
Accessing the wrong side of a Result (for example calling .value on an Err)
raises a ResultAccessError, making invalid access explicit and discoverable.
Errors¶
Foundation defines a structured error model.
Errors represent what went wrong, not how control flows.
They are:
- structured objects (message + metadata)
- debuggable
- composable and aggregatable
- usable both as return values and raised exceptions
Errors are commonly carried inside Err, but the two concepts are distinct:
Resultanswers whether an operation succeeded.Erroranswers why it failed.
Error building blocks¶
Foundation ships small, reusable data structures that compose the error model:
ErrorMessagecarries the human-readable message of an error.ErrorMetadatacarries additional context as adictof structured data.FieldReferenceidentifies the field an error refers to, when applicable.
These types are intentionally minimal so that any block can build on them
without inheriting domain assumptions.
Base error¶
Error is the base class for all structured errors. It:
- inherits from both
ExceptionandDebuggable, so it can be raised like a
standard exception and inspected like a debuggable object. - exposes
.message,.metadata, and a convenience.contextshortcut. - provides
as_debug_string()for rich, multi-line diagnostic output.
Specialized errors¶
Foundation provides a small set of reusable error categories that you can use
directly or extend:
NoneNotAllowedErrorindicates that aNonevalue was supplied where it
was not allowed.CantModifyImmutableAttributeErrorsignals an attempt to mutate an
attribute that was meant to stay immutable, such as on aValueObject.ValidationErrorandValidationFieldErrorsmodel input validation
failures, optionally tied to a specificFieldReference.RuleViolationErrorandCombinedRuleViolationErrorsmodel business rule
violations, with an aggregator for collecting several at once.CombinedErrorsis a generic aggregator for grouping anyErrorsubclass
under a single failure.FieldErrorsis a generic base for errors associated with a specific field
that can be iterated and counted like a collection.ResultAccessErroris raised when.valueor.erroris read from the
wrong side of aResult.
These error types are architecture-neutral and reusable across domains.
Port¶
A Port represents a boundary between components.
Ports define what is expected, not how it is implemented.
They enable replacement, testing, and decoupling without imposing an architecture.
Port is intentionally minimal: a generic Protocol parameterized by an
input and output type. The concrete meaning of a port is defined by the
components that extend it.
In addition to the base Port, Foundation exposes two pairs of role-marking
aliases that you can use to give a boundary a more specific semantic name:
InboundPortandInputPortdescribe boundaries that accept input from
the outside world.OutboundPortandOutputPortdescribe boundaries that produce output
towards the outside world.
These aliases are intentionally not prescriptive.
You are free to ignore them and use only Port, or to invent your own naming
on top of the same generic shape.
Mapper¶
Mapper defines explicit transformations between types.
It makes data conversion intentional and observable, avoiding hidden or
implicit mappings.
Mapper is a generic Protocol parameterized by a source type and a target
type. Variance is inferred automatically by the type checker, so the same
abstraction can be reused in a wide range of contexts.
Debuggable¶
Debuggable is a lightweight contract for exposing structured debug information.
It enables introspection without leaking internal state or implementation details.
ValueObject¶
ValueObject is the base class for immutable concepts defined by their values.
A value object:
- is identified entirely by the values of its attributes, not by a unique id.
- is immutable after construction: any attempt to modify an attribute after
initialization raises aCantModifyImmutableAttributeError. - is hashable, so it can be safely used as a dictionary key or set member.
- implements equality based on its
_equality_components, which subclasses
define.
ValueObject lives in Foundation because immutability and value-based
equality are generic enough to be reused outside the Domain block as well.
The Domain block simply re-exports the same class so that domain code can
import it from a natural location.
In practice, you can import ValueObject from either
forging_blocks.foundation.value_object (where the implementation lives)
or from forging_blocks.domain.value_object (the re-export). It is not
re-exported from the top-level forging_blocks.foundation namespace.
Auto-freeze¶
@auto_freeze is a decorator that automatically freezes instances after
__init__ completes, enforcing immutability without requiring subclasses
to call _freeze() or add any freeze-related code.
ValueObject uses @auto_freeze internally so that every concrete subclass
gets immutability for free. You can also apply @auto_freeze to your own
classes that need automatic immutability but are not ValueObject
subclasses.
A class decorated with @auto_freeze must satisfy the SupportsAutoFreeze
protocol by providing three methods:
freeze_instance()— transition the instance into an immutable state.unfreeze_instance()— transition back to mutable (intended for test
setup and transaction rollback; not for normal application code).should_use_internal_freezing()— classmethod that returnsTruefor
concrete classes andFalsefor abstract intermediate classes that
should stay unfrozen.
Classes that implement freeze_attributes(attrs) can also opt into
partial freezing, where only specific attributes (e.g. "_id") are
made immutable while others remain writable. This is useful for Entities
where identity must be fixed but other fields may change over time.
Usage:
from forging_blocks.foundation.autofreeze import auto_freeze
@auto_freeze
class MyImmutableClass:
def __init__(self, name: str) -> None:
self._name = name
def freeze_instance(self) -> None:
object.__setattr__(self, "__frozen", True)
def unfreeze_instance(self) -> None:
object.__setattr__(self, "__frozen", False)
@classmethod
def should_use_internal_freezing(cls) -> bool:
return True
def __setattr__(self, name: str, value: object) -> None:
if getattr(self, "__frozen", False):
msg = (
f"Cannot modify attribute '{name}' on "
f"frozen instance of '{type(self).__name__}'"
)
raise AttributeError(msg)
object.__setattr__(self, name, value)
Most users won't need @auto_freeze directly — extending ValueObject is
the simpler path. The decorator is available for cases where you want
automatic immutability without value-object semantics.
Meta utilities¶
Foundation provides a small set of metaclass utilities for runtime
enforcement of design intent.
These tools complement static type-checker hints with checks that run
at class creation time.
runtime_finalis a decorator that marks a method as final at runtime.
Any subclass that tries to override the method fails at class creation
time, not later.FinalMetais a metaclass that enforces@runtime_finaldeclarations
across an inheritance hierarchy.FinalABCMetacombinesFinalMetawithABCMeta, so a class can be
both abstract and runtime-final at the same time.
These utilities are optional.
You can use them to express intent clearly, but you are never required
to use them in order to follow the rest of the toolkit.
Identified¶
Identified is a protocol for any object that carries an identifier.
It exposes an id property that returns the object's identity, which may be None for unpersisted instances.
Any object whose id returns its identity satisfies this protocol, making it useful for generic contracts across repositories and infrastructure without coupling to a specific block.
Messages¶
The Foundation block provides Messages as reusable abstractions for expressing intent, facts, and queries within the problem space.
Messages are immutable and architecture-neutral, making them suitable for use across all blocks in the system.
Message¶
A Message is the base abstraction for all messages.
It encapsulates:
- An immutable payload, exposed through the
_payloadproperty that
subclasses implement. - A
MessageMetadatainstance with identity, timestamps, and correlation
information.
Message also exposes convenience shortcuts over its metadata:
message_idreturns the unique identifier of the message.created_atreturns the timestamp at which the message was created.to_dict()returns a dictionary that combines the metadata and the
payload, useful for serialization.
Equality between messages is based on their message_id, so two messages
with identical content but different identities are still considered
different.
Message is a base class and is meant to be subclassed rather than used
directly. The Foundation block provides three concrete base classes for the
common roles: Command, Event, and Query.
MessageMetadata is intentionally separated from data to avoid leaking
technical concerns into foundation logic. It carries:
message_id, the unique identifier of the message.created_at, the timestamp at which the message was created.message_type, the name of the message's class by default.correlation_id, an identifier used to group related messages that
belong to the same business process.causation_id, the identifier of the message that immediately
preceded this one.
Command¶
A Command represents an intent to perform an action.
Characteristics:
- Imperative meaning
- Expresses a request to change state
- May be accepted or rejected
Commands model what someone wants to do in the application.
Commands also expose two convenience properties over their metadata:
command_idis the unique identifier of the command, equal tomessage_id.issued_atis the timestamp at which the command was issued, equal to
created_at.
These names are provided as semantic aliases so that intent is obvious
in places that talk about commands specifically.
Event¶
An Event represents a fact that has already occurred.
Characteristics:
- Expressed in the past tense
- Immutable and irreversible
- Records something that happened
Events model what is known to be true after a state transition.
Events also expose occurred_at, a semantic alias for the timestamp at which
the event was created. The name is meant to reflect how events are usually
read: as something that happened at a specific moment.
Query¶
A Query represents an intent to retrieve information.
Characteristics:
- Does not modify state
- Expresses interest, not behavior
- Side-effect free
Queries model what someone wants to know.
Influence: CQRS literature
The distinction between Commands, Events, and Queries is inspired by CQRS concepts described by multiple authors, including Greg Young and Vaughn Vernon.
ForgingBlocks treats these as semantic roles, not architectural mandates.
Characteristics¶
- Pure Python (standard library only).
- Architecture-neutral.
- Stable, low-level abstractions.
- Used by all other blocks.