{"id":46286011,"url":"https://github.com/mom1/dddkit","last_synced_at":"2026-03-04T07:01:39.023Z","repository":{"id":322172705,"uuid":"1087729968","full_name":"mom1/dddkit","owner":"mom1","description":"Kit for using DDD tactical patterns","archived":false,"fork":false,"pushed_at":"2025-11-24T08:02:30.000Z","size":1637,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-08T19:16:45.762Z","etag":null,"topics":["ddd","ddd-patterns","stories","usecase"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mom1.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-01T14:23:34.000Z","updated_at":"2025-12-12T10:54:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mom1/dddkit","commit_stats":null,"previous_names":["mom1/dddkit"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/mom1/dddkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mom1%2Fdddkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mom1%2Fdddkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mom1%2Fdddkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mom1%2Fdddkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mom1","download_url":"https://codeload.github.com/mom1/dddkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mom1%2Fdddkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30075425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T05:31:57.858Z","status":"ssl_error","status_checked_at":"2026-03-04T05:31:38.462Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ddd","ddd-patterns","stories","usecase"],"created_at":"2026-03-04T07:01:33.756Z","updated_at":"2026-03-04T07:01:39.003Z","avatar_url":"https://github.com/mom1.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DDDKit\n\n\u003cdiv align=\"center\"\u003e\n    \u003cpicture\u003e\n      \u003cimg src=\"https://raw.githubusercontent.com/mom1/dddkit/main/static/kid_ddd.png\"\n        alt=\"DDDKit\" style=\"width: 50%; height: auto;\" /\u003e\n    \u003c/picture\u003e\n\u003c/div\u003e\n\n[![PyPI](https://img.shields.io/pypi/v/dddkit.svg)](https://pypi.org/project/dddkit/)\n[![Python Version](https://img.shields.io/pypi/pyversions/dddkit.svg)](https://pypi.org/project/dddkit/)\n![PyPI - Downloads](https://img.shields.io/pypi/dm/dddkit.svg?label=pip%20installs\u0026logo=python)\n\n[![Gitmoji](https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg)](https://gitmoji.dev)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)\n[![UV](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://docs.astral.sh/uv/)\n\n![GitHub issues](https://img.shields.io/github/issues/mom1/dddkit.svg)\n![GitHub stars](https://img.shields.io/github/stars/mom1/dddkit.svg)\n![GitHub Release Date](https://img.shields.io/github/release-date/mom1/dddkit.svg)\n![GitHub commits since latest release](https://img.shields.io/github/commits-since/mom1/dddkit/latest.svg)\n![GitHub last commit](https://img.shields.io/github/last-commit/mom1/dddkit.svg)\n[![GitHub license](https://img.shields.io/github/license/mom1/dddkit)](https://github.com/mom1/dddkit/blob/master/LICENSE)\n\nKit for using DDD (Domain-Driven Design) tactical patterns in Python.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Installation](#installation)\n  - [Prerequisites](#prerequisites)\n  - [Installing dddkit](#installing-dddkit)\n  - [For Development](#for-development)\n- [Usage](#usage)\n  - [Basic Usage](#basic-usage)\n    - [Using dataclasses implementation](#using-dataclasses-implementation)\n    - [Using pydantic implementation](#using-pydantic-implementation)\n  - [Aggregate Events](#aggregate-events)\n  - [Event Handling](#event-handling)\n  - [Stories](#stories)\n    - [Basic Story Usage](#basic-story-usage)\n    - [Stories with Async Operations](#stories-with-async-operations)\n    - [Stories with Hooks](#stories-with-hooks)\n- [Prometheus Integration](#prometheus-integration)\n  - [Available Hook Classes](#available-hook-classes)\n  - [Installation](#installation-1)\n  - [Standard Prometheus Hook](#standard-prometheus-hook)\n  - [AIOPrometheus Hook](#aioprometheus-hook)\n  - [Common Metrics Exposed](#common-metrics-exposed)\n  - [Key Differences](#key-differences)\n  - [Grafana Dashboards](#grafana-dashboards)\n- [Project Structure](#project-structure)\n- [Contributing](#contributing)\n  - [Development Commands](#development-commands)\n- [License](#license)\n- [Development Status](#development-status)\n\n## Overview\n\n`dddkit` is a Python library designed to facilitate the implementation of Domain-Driven Design tactical patterns. It\nprovides base classes and utilities for common DDD concepts such as Aggregates, Entities, Value Objects, Domain Events,\nand Repositories.\n\nThe library offers both `dataclasses` and `pydantic` implementations of DDD patterns to accommodate different project\nneeds and preferences.\n\n## Features\n\n- **Aggregate**: Base class for DDD aggregates with event handling capabilities\n- **Entity**: Base class for entities with identity\n- **ValueObject**: Base class for value objects without identity\n- **Domain Events**: Support for domain event creation and handling\n- **Event Brokers**: Synchronous and asynchronous event brokers for event processing\n- **Repositories**: Base repository pattern implementation\n- **Changes Handler**: Mechanism to handle aggregate changes and events\n- **Stories**: A pattern for defining and executing sequential business operations with hooks and execution tracking\n\n## Installation\n\n### Prerequisites\n\nThis project uses [uv](https://docs.astral.sh/uv/) for Python and dependency management. Install it first:\n\n```bash\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n```\n\nOr with [brew](https://brew.sh/) on macOS:\n\n```bash\nbrew install uv\n```\n\n### Installing dddkit\n\nInstall with uv from PyPI:\n\n```bash\nuv pip install dddkit\n```\n\nOr with pip:\n\n```bash\npip install dddkit\n```\n\n### For Development\n\nTo set up the development environment:\n\n```bash\n# Clone the repository\ngit clone https://github.com/mom1/dddkit.git\n\n# Navigate to the project directory\ncd dddkit\n\n# Install dependencies\nmake install\n```\n\n## Usage\n\n### Basic Usage\n\nThe library provides two implementations of DDD patterns:\n\n1. **dataclasses**: Using Python's built-in `dataclasses`\n2. **pydantic**: Using the `pydantic` library (optional dependency)\n\n#### Using dataclasses implementation\n\n```python\nfrom typing import NewType\nfrom dataclasses import dataclass, field\nfrom dddkit.dataclasses import Aggregate, Entity\n\nProductName = NewType('ProductName', str)\nProductId = NewType('ProductId', int)\nBasketId = NewType('BasketId', int)\n\n\n@dataclass(kw_only=True)\nclass Product(Entity):\n  product_id: ProductId\n  name: ProductName\n  amount: float = 0\n\n\n@dataclass(kw_only=True)\nclass Basket(Aggregate):\n  basket_id: BasketId\n  items: dict[ProductId, Product] = field(default_factory=dict)\n\n  @classmethod\n  def new(cls, basket_id: BasketId):\n    return cls(basket_id=basket_id)\n\n  def add_item(self, item: Product):\n    if _item := self.items.get(item.product_id):\n      _item.amount = item.amount\n\n\n# Use repositories and event handling\nfrom dddkit.dataclasses import Repository\n\n\nclass BasketRepository(Repository[Basket, BasketId]):\n  \"\"\"Repository for basket\"\"\"\n```\n\n#### Using pydantic implementation\n\nFirst install the optional pydantic dependency:\n\n```bash\nuv pip install dddkit[pydantic]\n```\n\n```python\nfrom typing import NewType\nfrom dddkit.pydantic import Aggregate, Entity, AggregateEvent\nfrom pydantic import Field\n\nProductName = NewType('ProductName', str)\nProductId = NewType('ProductId', int)\nBasketId = NewType('BasketId', int)\n\n\nclass Product(Entity):\n  product_id: ProductId\n  name: ProductName\n  amount: float = 0\n\n\nclass Basket(Aggregate):\n  basket_id: BasketId\n  items: dict[ProductId, Product] = Field(default_factory=dict)\n\n  @classmethod\n  def new(cls, basket_id: BasketId):\n    return cls(basket_id=basket_id)\n\n  def add_item(self, item: Product):\n    if _item := self.items.get(item.product_id):\n      _item.amount = item.amount\n\n\n# Use repositories and event handling\nfrom dddkit.pydantic import Repository\n\n\nclass BasketRepository(Repository[Basket, BasketId]):\n  \"\"\"Repository for basket\"\"\"\n```\n\n### Aggregate Events\n\n```python\nfrom typing import NewType\nfrom dataclasses import dataclass, field\nfrom dddkit.dataclasses import Aggregate, Entity, AggregateEvent\n\nProductName = NewType('ProductName', str)\nProductId = NewType('ProductId', int)\nBasketId = NewType('BasketId', int)\n\n\n@dataclass(kw_only=True)\nclass Product(Entity):\n  product_id: ProductId\n  name: ProductName\n  amount: float = 0\n\n\n@dataclass(kw_only=True)\nclass Basket(Aggregate):\n  basket_id: BasketId\n  items: dict[ProductId, Product] = field(default_factory=dict)\n\n  @dataclass(frozen=True, kw_only=True)\n  class Created(AggregateEvent):\n    \"\"\"Basket created event\"\"\"\n\n  @dataclass(frozen=True, kw_only=True)\n  class AddedItem(AggregateEvent):\n    item: Product\n\n  @classmethod\n  def new(cls, basket_id: BasketId):\n    basket = cls(basket_id=basket_id)\n    basket.add_event(cls.Created())\n    return basket\n\n  def add_item(self, item: Product):\n    if _item := self.items.get(item.product_id):\n      _item.amount = item.amount\n      self.add_event(self.AddedItem(item=_item))\n```\n\n### Event Handling\n\n```python\nfrom dddkit.dataclasses import EventBroker\n\nhandle_event = EventBroker()\n\n\n# sync\n\n@handle_event.handle(ProductCreated)\ndef _(event: ProductCreated):\n  # Handle the event\n  print(f\"Product {event.name} created with ID {event.product_id}\")\n\n\nproduct_event = ProductCreated(product_id=ProductId(\"123\"), name=\"Test Product\")\n\n\ndef context():\n  handle_event(product_event)\n\n\n# Or async\n\n@handle_event.handle(ProductCreated)\nasync def _(event: ProductCreated):\n  # Handle the event\n  print(f\"Product {event.name} created with ID {event.product_id}\")\n\n\nasync def context():\n  await handle_event(product_event)\n```\n\n### Stories\n\nStories provide a pattern for defining sequential business operations with optional hooks for execution tracking,\nlogging, and timing.\n\n\u003e **Note**: The stories implementation in DDDKit was inspired by and uses parts of the work\n\u003e from [proofit404/stories](https://github.com/proofit404/stories).\n\n#### Basic Story Usage\n\n```python\nfrom dataclasses import dataclass\nfrom dddkit.stories import I, Story\nfrom types import SimpleNamespace\n\n\n@dataclass(frozen=True, slots=True)\nclass ShoppingCartStory(Story):\n  # Define the steps in the story\n  I.add_item\n  I.apply_discount\n  I.calculate_total\n\n  class State(SimpleNamespace):\n    items: list = []\n    discount: float = 0.0\n    total: float = 0.0\n\n  def add_item(self, state: State):\n    state.items.append({\"name\": \"Product A\", \"price\": 10.0})\n\n  def apply_discount(self, state: State):\n    if len(state.items) \u003e 1:\n      state.discount = 0.1  # 10% discount\n\n  def calculate_total(self, state: State):\n    subtotal = sum(item[\"price\"] for item in state.items)\n    state.total = subtotal * (1 - state.discount)\n\n\n# Execute the story\nstory = ShoppingCartStory()\nstate = story.State()\nstory(state)\n\nprint(f\"Items: {state.items}\")\nprint(f\"Discount: {state.discount}\")\nprint(f\"Total: {state.total}\")\n```\n\n#### Stories with Async Operations\n\nStories support both synchronous and asynchronous operations:\n\n```python\nimport asyncio\nfrom dataclasses import dataclass\nfrom dddkit.stories import I, Story\nfrom types import SimpleNamespace\n\n\n@dataclass(frozen=True, slots=True)\nclass AsyncProcessingStory(Story):\n  I.fetch_data\n  I.process_data\n  I.save_result\n\n  class State(SimpleNamespace):\n    raw_data: str = \"\"\n    processed_data: str = \"\"\n    saved: bool = False\n\n  async def fetch_data(self, state: State):\n    # Simulate async data fetching\n    await asyncio.sleep(0.1)\n    state.raw_data = \"some raw data\"\n\n  def process_data(self, state: State):\n    state.processed_data = state.raw_data.upper()\n\n  async def save_result(self, state: State):\n    # Simulate async saving\n    await asyncio.sleep(0.05)\n    state.saved = True\n\n\n# Execute the async story\nasync def run_async_story():\n  story = AsyncProcessingStory()\n  state = story.State()\n  await story(state)\n  return state\n\n# asyncio.run(run_async_story())\n```\n\n#### Stories with Hooks\n\nStories support hooks for execution tracking, logging, and performance monitoring:\n\n```python\nfrom dataclasses import dataclass\nfrom dddkit.stories import I, Story, inject_hooks, ExecutionTimeTracker, StatusTracker, LoggingHook\nfrom types import SimpleNamespace\n\n\n@dataclass(frozen=True, slots=True)\nclass HookedStory(Story):\n  I.step_one\n  I.step_two\n  I.step_three\n\n  class State(SimpleNamespace):\n    step_one_completed: bool = False\n    step_two_completed: bool = False\n    step_three_completed: bool = False\n\n  def step_one(self, state: State):\n    state.step_one_completed = True\n\n  def step_two(self, state: State):\n    state.step_two_completed = True\n\n  def step_three(self, state: State):\n    state.step_three_completed = True\n\n\n# Inject default hooks (StatusTracker, ExecutionTimeTracker, LoggingHook)\nstory_class = HookedStory\ninject_hooks(story_class)\n\n# Execute the story with hooks\nstory = story_class()\nstate = story.State()\nstory(state)\n```\n\n```shell\n# At the DEBUG log level, you will see the process of executing story steps.\nHookedStory:\n    ⟳I.step_one\n    I.step_two\n    I.step_three\nHookedStory:\n    ✓I.step_one [0.000s]\n    ⟳I.step_two\n    I.step_three\nHookedStory:\n    ✓I.step_one [0.000s]\n    ✓I.step_two [0.001s]\n    ⟳I.step_three\n# If an error occurs during the execution of a story, it will look like this\nHookedStory:\n    ✓I.step_one [0.000s]\n    ✓I.step_two [0.001s]\n    ✗I.step_three\nTraceback (most recent call last):\n  File \"/your_file.py\", line 115, in your_function\n  ...\nexceptions.YourException\n```\n\nStories provide three types of hooks:\n\n- `before`: Runs before each step\n- `after`: Runs after each step (even if exceptions occur)\n- `error`: Runs when an exception occurs in a step\n\nYou can also create custom hooks:\n\n```python\nfrom dddkit.stories import StoryExecutionContext, StepExecutionInfo, inject_hooks\n\n\nclass CustomHook:\n  def before(self, context: StoryExecutionContext, step_info: StepExecutionInfo):\n    print(f\"Starting step: {step_info.step_name}\")\n\n  def after(self, context: StoryExecutionContext, step_info: StepExecutionInfo):\n    print(f\"Completed step: {step_info.step_name}\")\n\n  def error(self, context: StoryExecutionContext, step_info: StepExecutionInfo):\n    print(f\"Error in step: {step_info.step_name}, Error: {step_info.error}\")\n\n\n# Inject custom hooks\ninject_hooks(HookedStory, hooks=[CustomHook()])\n```\n\n## Prometheus Integration\n\nDDDKit provides comprehensive Prometheus integration through specialized metrics hooks that collect and expose metrics\nfor story execution, providing observability and performance monitoring for your DDDKit story operations.\n\n### Available Hook Classes\n\nDDDKit offers two Prometheus metrics hooks depending on your application's needs:\n\n1. **`dddkit.stories.prometheus.hook.PrometheusMetricsHook`**: Uses the standard `prometheus_client` library\n2. **`dddkit.stories.aioprometheus.hook.PrometheusMetricsHook`**: Uses the `aioprometheus` library for asynchronous\n   environments\n\n### Installation\n\nFor the standard Prometheus hook, install the optional prometheus dependency:\n\n```bash\nuv pip install dddkit[prometheus]\n```\n\nOr with pip:\n\n```bash\npip install dddkit[prometheus]\n```\n\nFor the async-friendly hook, install the aioprometheus dependency:\n\n```bash\nuv pip install dddkit[aioprometheus]\n```\n\nOr with pip:\n\n```bash\npip install dddkit[aioprometheus]\n```\n\n### Standard Prometheus Hook\n\nThe `PrometheusMetricsHook` from the `dddkit.stories.prometheus` module uses the standard `prometheus_client` library.\n\n#### Usage\n\n```python\nfrom dataclasses import dataclass\nfrom dddkit.stories import I, Story, inject_hooks\nfrom dddkit.stories.prometheus import PrometheusMetricsHook\nfrom types import SimpleNamespace\n\n\n@dataclass(frozen=True, slots=True)\nclass MonitoredStory(Story):\n  I.step_one\n  I.step_two\n  I.step_three\n\n  class State(SimpleNamespace):\n    step_one_completed: bool = False\n    step_two_completed: bool = False\n    step_three_completed: bool = False\n\n  def step_one(self, state: State):\n    state.step_one_completed = True\n\n  def step_two(self, state: State):\n    state.step_two_completed = True\n\n  def step_three(self, state: State):\n    state.step_three_completed = True\n\n\n# Create an instance of PrometheusMetricsHook\nprometheus_hook = PrometheusMetricsHook(\n  app_name=\"my_app\",\n  prefix=\"my_service\",\n  labels={\"env\": \"production\", \"version\": \"1.0.0\"},\n  buckets=[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]  # in milliseconds\n)\n\n# Inject the hook into your story class\ninject_hooks(MonitoredStory, hooks=[prometheus_hook])\n\n# Execute the story\nstory = MonitoredStory()\nstate = story.State()\nstory(state)\n```\n\n#### Configuration Options\n\nThe standard PrometheusMetricsHook class accepts the following configuration parameters:\n\n- `app_name` (str, default: 'dddkit_stories'): The name of the service to use in the metrics\n- `prefix` (str, default: 'dddkit_stories'): The prefix to use for the metrics\n- `labels` (dict[str, str], default: {}): A mapping of labels to add to the metrics\n- `buckets` (list[str | float] | None, default: None): A list of buckets to use for the histogram. If not provided,\n  defaults to [10, 25, 50, 100, 300, 500, 1000, 2000, 5000, 10000] milliseconds\n\n### AIOPrometheus Hook\n\nThe `PrometheusMetricsHook` from the `dddkit.stories.aioprometheus` module uses the `aioprometheus` library and is more\nsuitable for asynchronous applications.\n\n#### Usage\n\n```python\nfrom dataclasses import dataclass\nfrom dddkit.stories import I, Story, inject_hooks\nfrom dddkit.stories.aioprometheus import PrometheusMetricsHook\nfrom types import SimpleNamespace\n\n\n@dataclass(frozen=True, slots=True)\nclass AsyncMonitoredStory(Story):\n  I.step_one\n  I.step_two\n  I.step_three\n\n  class State(SimpleNamespace):\n    step_one_completed: bool = False\n    step_two_completed: bool = False\n    step_three_completed: bool = False\n\n  def step_one(self, state: State):\n    state.step_one_completed = True\n\n  def step_two(self, state: State):\n    state.step_two_completed = True\n\n  def step_three(self, state: State):\n    state.step_three_completed = True\n\n\n# Create an instance of AIOPrometheusMetricsHook\nprometheus_hook = PrometheusMetricsHook(\n  app_name=\"my_async_app\",\n  prefix=\"my_async_service\",\n  labels={\"env\": \"production\", \"version\": \"1.0.0\"},\n  buckets=[5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000]  # in milliseconds\n)\n\n# Inject the hook into your story class\ninject_hooks(AsyncMonitoredStory, hooks=[prometheus_hook])\n\n# Execute the story\nstory = AsyncMonitoredStory()\nstate = story.State()\nstory(state)\n```\n\n#### Configuration Options\n\nThe AIOPrometheusMetricsHook class accepts similar configuration parameters:\n\n- `app_name` (str, default: 'dddkit_stories'): The name of the service to use in the metrics\n- `prefix` (str, default: 'dddkit_stories'): The prefix to use for the metrics\n- `labels` (dict[str, str], default: {}): A mapping of labels to add to the metrics\n- `buckets` (list[float] | None, default: None): A list of buckets to use for the histogram. If not provided,\n  defaults to [10.0, 25.0, 50.0, 100.0, 300.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0] milliseconds\n\n### Common Metrics Exposed\n\nBoth Prometheus hooks expose the following Prometheus metrics:\n\n- `dddkit_stories_executions_latency_ms` - Histogram metric tracking total story execution time\n  - Labels: `service`, `story_name`, `status`, and any custom labels\n  - Help text: \"Story Execution Time\"\n\n- `dddkit_stories_step_executions_latency_ms` - Histogram metric tracking individual step execution time\n  - Labels: `service`, `story_name`, `step_name`, `status`, and any custom labels\n  - Help text: \"Story step execution time\"\n\n### Key Differences\n\nThe main difference between the two hooks is the underlying Prometheus library they use:\n\n- Standard hook uses `prometheus_client` library and is suitable for synchronous applications\n- AIOPrometheus hook uses `aioprometheus` library and provides better integration with async frameworks\n\n### Grafana Dashboards\n\nExample Grafana dashboards are provided in the `.grafana` folder to visualize the metrics exposed by the\nPrometheus hooks, along with screenshot previews:\n\n- `stories-execution-dashboard.json` - Dashboard showing overall story execution metrics including success rate, total\n  executions, execution status, latency percentiles, and execution trends over time\n- `stories-steps-execution-dashboard.json` - Dashboard showing detailed metrics for individual story steps including\n  step duration, execution count, latency percentiles, and error tracking\n- `stories.png` - Screenshot preview of the stories execution dashboard\n- `stories_steps.png` - Screenshot preview of the stories steps execution dashboard\n\nThese screenshots provide visual examples of what the dashboards look like when properly configured with Prometheus\nmetrics from DDDKit stories.\n\n## Project Structure\n\n```shell\nsrc/dddkit/\n├── __init__.py\n├── dataclasses/        # DDD patterns using dataclasses\n│   ├── __init__.py\n│   ├── aggregates.py\n│   ├── changes_handler.py\n│   ├── events.py\n│   └── repositories.py\n├── pydantic/          # DDD patterns using pydantic\n│   ├── __init__.py\n│   ├── aggregates.py\n│   ├── changes_handler.py\n│   ├── events.py\n│   └── repositories.py\n└── stories/           # Stories pattern for sequential operations\n    ├── __init__.py\n    ├── story.py       # Core Story implementation\n    └── hooks.py       # Hook implementations for stories\n```\n\n## Contributing\n\nContributions are welcome! Here's how you can get started:\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Make your changes\n4. Add tests if applicable\n5. Run the test suite (`make test`)\n6. Commit your changes (`git commit -m 'Add amazing feature'`)\n7. Push to the branch (`git push origin feature/amazing-feature`)\n8. Open a Pull Request\n\n### Development Commands\n\n```shell\nmake install    # Install dependencies\nmake test       # Run tests\nmake lint       # Run linter\nmake format     # Run formatter\nmake build      # Build the package\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Development Status\n\nThis project is in production/stable state. All contributions and feedback are welcome.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmom1%2Fdddkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmom1%2Fdddkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmom1%2Fdddkit/lists"}