Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Indroic/HexCore/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through a complete end-to-end example using HexCore’s core building blocks: a BaseEntity domain entity, a Command, an AbstractCommandHandler, a HandlerRegistry, and an InMemoryCommandBus. By the end you will have a runnable Python script that models a Product aggregate and handles a CreateProduct command entirely in memory — perfect for unit-testing your domain logic without any database.
1
Install HexCore
2
Install the package from PyPI. Core dependencies (Pydantic v2, Typer, croniter) are included automatically.
3
pip
pip install hexcore
uv
uv add hexcore
4
Python 3.12 or later is required. For database, API, or message-queue support see the Installation guide for the available extras.
5
Define a domain entity
6
Every aggregate root in HexCore extends BaseEntity. It ships with four built-in fields (id, created_at, updated_at, is_active) so you only need to add the fields that belong to your domain model.
7
from uuid import UUID
from hexcore.domain.base import BaseEntity

class Product(BaseEntity):
    """A simple product aggregate root."""
    name: str
    price: float
    stock: int = 0
8
BaseEntity is backed by Pydantic v2 with validate_assignment=True, so field values are validated every time they change. The id field defaults to a fresh uuid4, and both timestamp fields default to datetime.now(UTC).
9
Define a command
10
A Command represents an intent to change state. It is immutable (frozen=True) and carries a command_id and timestamp automatically.
11
from hexcore.domain.cqrs.commands import Command

class CreateProductCommand(Command):
    name: str
    price: float
    initial_stock: int = 0
12
Commands are Pydantic models — you get automatic validation, serialization, and type-safety for free. Pass them directly to your API request schemas if you like.
13
Write a command handler
14
AbstractCommandHandler is a generic ABC with a single abstract method: handle(command). The type parameters declare the command type and the return type; use None for fire-and-forget commands.
15
from hexcore.domain.cqrs.handlers import AbstractCommandHandler
from hexcore.domain.cqrs.commands import Command

class CreateProductHandler(AbstractCommandHandler[CreateProductCommand, Product]):
    """Handles the CreateProductCommand and returns the new Product entity."""

    async def handle(self, command: CreateProductCommand) -> Product:
        product = Product(
            name=command.name,
            price=command.price,
            stock=command.initial_stock,
        )
        # In a real application you would persist via a repository here:
        # product = await self.repository.save(product)
        return product
16
Register the handler and wire up the bus
17
HandlerRegistry maps each command type to its handler. Pass the registry to InMemoryCommandBus and you have a fully working command bus — no configuration files required.
18
from hexcore.application.cqrs.registry import HandlerRegistry
from hexcore.application.cqrs.in_memory_buses import InMemoryCommandBus

registry = HandlerRegistry()
registry.register_command_handler(CreateProductCommand, CreateProductHandler())

bus = InMemoryCommandBus(registry=registry)
19
Dispatch a command
20
Call await bus.dispatch(command) to run the full handler pipeline and receive the result.
21
import asyncio

async def main() -> None:
    command = CreateProductCommand(
        name="Wireless Keyboard",
        price=49.99,
        initial_stock=100,
    )
    product: Product = await bus.dispatch(command)
    print(f"Created product: {product.id}{product.name} @ ${product.price}")

asyncio.run(main())

Complete working example

Below is the full script combining all of the steps above. Copy it into a file named demo.py, run pip install hexcore, then execute python demo.py.
"""
HexCore quickstart demo — no database required.
Run: python demo.py
"""
import asyncio
from uuid import UUID

from hexcore.domain.base import BaseEntity
from hexcore.domain.cqrs.commands import Command
from hexcore.domain.cqrs.handlers import AbstractCommandHandler
from hexcore.application.cqrs.registry import HandlerRegistry
from hexcore.application.cqrs.in_memory_buses import InMemoryCommandBus


# 1. Domain entity
class Product(BaseEntity):
    name: str
    price: float
    stock: int = 0


# 2. Command (immutable intent)
class CreateProductCommand(Command):
    name: str
    price: float
    initial_stock: int = 0


# 3. Handler (business logic)
class CreateProductHandler(AbstractCommandHandler[CreateProductCommand, Product]):
    async def handle(self, command: CreateProductCommand) -> Product:
        return Product(
            name=command.name,
            price=command.price,
            stock=command.initial_stock,
        )


# 4. Wire up the bus
registry = HandlerRegistry()
registry.register_command_handler(CreateProductCommand, CreateProductHandler())
bus = InMemoryCommandBus(registry=registry)


# 5. Dispatch
async def main() -> None:
    cmd = CreateProductCommand(name="Wireless Keyboard", price=49.99, initial_stock=100)
    product: Product = await bus.dispatch(cmd)
    print(f"Product ID : {product.id}")
    print(f"Name       : {product.name}")
    print(f"Price      : ${product.price}")
    print(f"Stock      : {product.stock}")
    print(f"Created at : {product.created_at}")
    print(f"Is active  : {product.is_active}")


asyncio.run(main())

Next steps

Installation

Add optional extras for FastAPI, SQLAlchemy, Redis, MongoDB, or task queues.

CQRS Pattern

Learn about queries, event buses, middlewares, and smart background routing.

Domain-Driven Design

Explore entities, value objects, domain events, repositories, and services.

Hexagonal Architecture

Understand ports, adapters, and how HexCore organises the three layers.

Build docs developers (and LLMs) love