Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ce6e756
build using nightly
tommyengstrom Apr 29, 2025
b99b195
use latest nightly
tommyengstrom Apr 29, 2025
6b8ee0c
towards indexed model support
tommyengstrom Jun 25, 2025
5ec5845
it compiles again
tommyengstrom Jun 26, 2025
d256859
Fix test suite
tommyengstrom Jun 26, 2025
bc991e2
Get test suite to pass
tommyengstrom Jun 26, 2025
d6dd309
Writing extra tests
tommyengstrom Jun 26, 2025
5d392b6
Sequential writes on same index works
tommyengstrom Jun 26, 2025
1db7e5e
Use advisory locks
tommyengstrom Jun 26, 2025
3a399fb
fix dependencies
tommyengstrom Jun 26, 2025
92cdf12
most consistent parameter order
tommyengstrom Jun 26, 2025
35e9e54
export indexed handlers
tommyengstrom Jun 26, 2025
3b564c9
Clean up and change resolver
tommyengstrom Jul 10, 2025
41dcc50
tmp, experimenting
tommyengstrom Aug 27, 2025
3cfe68c
prototyping
tommyengstrom Sep 2, 2025
1345f4c
more prototyping
tommyengstrom Sep 2, 2025
f0bd9ea
clean up
tommyengstrom Sep 12, 2025
ed423e2
fix various things
tommyengstrom Sep 12, 2025
a46d232
remove model parameter for command in `runTransaction`
tommyengstrom Sep 12, 2025
0092d99
I think the effectful version works now, but I broke the old version
tommyengstrom Sep 12, 2025
f18d4b2
include ShapeCoercible
tommyengstrom Sep 13, 2025
935580d
use plugin and improve examples
tommyengstrom Sep 13, 2025
e232cb0
Make it compile and remove old domaindriven version
tommyengstrom Sep 13, 2025
293a0ac
tmp
tommyengstrom Sep 13, 2025
7b331ab
it compiles
tommyengstrom Sep 13, 2025
aff4e86
postgresql example
tommyengstrom Sep 13, 2025
d1ba237
migration example
tommyengstrom Sep 13, 2025
7272c54
minimal intro
tommyengstrom Sep 13, 2025
8ef9819
tmp
tommyengstrom Sep 15, 2025
fd2d67e
upgrade lts and include missings deps
tommyengstrom Sep 25, 2025
346ef05
switch to using cabal
tommyengstrom Sep 26, 2025
bcaa051
spec for how to support postgres state
tommyengstrom Sep 26, 2025
8f481a0
include readme
tommyengstrom Oct 1, 2025
1d80738
transactions get the current model as input
tommyengstrom Oct 9, 2025
71997ce
run-ghcid
tommyengstrom Nov 4, 2025
24e974b
add flake
tommyengstrom Feb 19, 2026
567fa2f
log hook failures
tommyengstrom Feb 19, 2026
4e25eed
improve sql generation
tommyengstrom Feb 19, 2026
2240d35
remove redundant model read
tommyengstrom Feb 19, 2026
fbc606e
test more
tommyengstrom Feb 20, 2026
1fef4b0
add hspec-discover dependency
tommyengstrom Feb 24, 2026
b498778
update CI to use nix flake dev shell with cabal
tommyengstrom Feb 24, 2026
a628bc0
add cabal update step and fix caching
tommyengstrom Feb 24, 2026
0976f0a
update documentation
tommyengstrom Feb 24, 2026
184d640
Make it easier for users to change pool settings
tommyengstrom Feb 24, 2026
e9a880a
refactor hooks
tommyengstrom Feb 25, 2026
15a3a2c
Better examples
tommyengstrom Feb 25, 2026
1dd490e
rename modules
tommyengstrom Feb 25, 2026
ef8c48a
make shape-coerce a separate package
tommyengstrom Feb 25, 2026
4eb8793
create initial domaindriven agent skill
tommyengstrom Mar 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "domaindriven",
"owner": {
"name": "Tommy Engström"
},
"plugins": [
{
"name": "domaindriven",
"source": ".",
"description": "Claude skill for the domaindriven Haskell event sourcing and CQRS library"
}
]
}
11 changes: 11 additions & 0 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "domaindriven",
"description": "Claude skill for the domaindriven Haskell event sourcing and CQRS library",
"version": "0.1.0",
"author": {
"name": "Tommy Engström"
},
"repository": "https://github.com/tommyengstrom/domaindriven",
"license": "BSD-3-Clause",
"keywords": ["haskell", "event-sourcing", "cqrs", "domain-driven-design", "effectful", "servant"]
}
13 changes: 13 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
]
}

],
"PostToolUse": [ ]
}
}
12 changes: 12 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"permissions": {
"allow": [
"WebFetch(domain:hackage.haskell.org)",
"WebFetch(domain:hackage-content.haskell.org)",
"WebFetch(domain:github.com)",
"WebFetch(domain:www.stackage.org)",
"Bash(cabal:*)"
],
"deny": []
}
}
58 changes: 25 additions & 33 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,13 @@ name: Run tests

on:
push:
branches: [master]
pull_request:
branches: ['*']

jobs:
test:
runs-on: ubuntu-latest

env:
COMPOSE_DOCKER_CLI_BUILD: 1
DOCKER_BUILDKIT: 1

services:
claims-db:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
Expand All @@ -29,31 +22,30 @@ jobs:
- 5432:5432

steps:
- uses: actions/checkout@v2
- name: ACTIONS_ALLOW_UNSECURE_COMMANDS
id: ACTIONS_ALLOW_UNSECURE_COMMANDS
run: echo 'ACTIONS_ALLOW_UNSECURE_COMMANDS=true' >> $GITHUB_ENV

- name: Update stack
run: stack update
- uses: actions/cache@v2
name: Cache ~/.stack
- uses: actions/checkout@v4

- uses: cachix/install-nix-action@v30
with:
path: ~/.stack
key: ${{ runner.os }}-stack-${{ hashFiles('stack.yaml') }}
- name: Build dependencies
run: |
stack build --only-dependencies --fast

- uses: actions/cache@v2
name: Cache .stack-work
nix_path: nixpkgs=channel:nixos-unstable

- uses: DeterminateSystems/magic-nix-cache-action@main

- name: Cache cabal
uses: actions/cache@v4
with:
path: .stack-work
key: ${{ runner.os }}-stack-${{ hashFiles('services/claims/package.yaml') }}
- name: Build domaindriven
run: |
stack build --fast
- name: Run tests
run: |
stack test --fast
path: |
~/.cabal/store
~/.cabal/packages
dist-newstyle
key: ${{ runner.os }}-cabal-${{ hashFiles('cabal.project', '**/*.cabal') }}
restore-keys: |
${{ runner.os }}-cabal-

- name: Update package index
run: nix develop --command cabal update

- name: Build
run: nix develop --command cabal build all

- name: Run tests
run: nix develop --command cabal test all
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ stack.yaml.lock
hie.yaml
dist-newstyle/
.vscode
.repro
ai_docs
53 changes: 53 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

- **Build all packages**: `cabal build all`
- **Build specific package**: `cabal build domaindriven`
- **Run tests**: `cabal test all`
- **Run specific test**: `cabal test domaindriven`
- **Clean build**: `cabal clean`

## Architecture Overview

DomainDriven is a synchronous event sourcing and CQRS library split into multiple packages:

### Core Components

- **domaindriven-core**: Core persistence model with PostgreSQL and in-memory backends
- `ReadModel`/`WriteModel` type classes define the persistence interface
- `ForgetfulInMemory`: In-memory backend for testing/development
- `Postgres`: Production persistence with transactional guarantees
- Synchronous event sourcing with locks to avoid eventual consistency issues

- **domaindriven**: Effectful-based API layer
- Uses standard Servant combinators instead of custom ones
- Two main effects: `Aggregate` (commands) and `Projection` (queries)
- Dynamic dispatch interpreters for different backends
- Located in `domaindriven/`

### Key Design Patterns

1. **Event Sourcing Model**:
- Model = current state (derived from events)
- Events = immutable history of changes
- `applyEvent :: Model -> Stored Event -> Model`

2. **Command/Query Separation**:
- Commands emit events and may update state
- Queries are read-only operations
- Both can be composed hierarchically

3. **Index Types**:
- `NoIndex` for single aggregates
- Custom index types for multiple aggregates
- Functional dependencies can reduce type parameters

## Development Notes

- All packages use extensive language extensions (see .cabal files)
- Strict warning settings (`-Wall -Werror`) - fix all warnings before committing
- The Effectful layer aims to simplify the API while maintaining type safety
- Tests use `hspec` framework with in-memory backends
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# DomainDriven

DomainDriven is a batteries included synchronous event sourcing and CQRS library. It is split into two parts:
DomainDriven is a batteries included synchronous event sourcing and CQRS library. It is split into the following packages:

- [domaindriven-core](domaindriven-core) Contains the core persistance model as well as postgres and in-memory backend.
- [domaindriven](domaindriven) Introduces a convenient way of specifying actions using GADTs and TemplateHaskell.
- [domaindriven-core](domaindriven-core) - Core persistence model with PostgreSQL and in-memory backends.
- [domaindriven](domaindriven) - Effectful-based API layer with `Aggregate` and `Projection` effects, plus Servant integration.
- [domaindriven-examples](domaindriven-examples) - Example applications demonstrating usage.

## Design idea

The core idea is to do synchronous event sourcing with locks and thereby provide the upsides of event sourcing without the extra complexity introduced by asynchronous workflows.
10 changes: 10 additions & 0 deletions cabal.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
packages:
shape-coerce/
domaindriven-core/
domaindriven/
domaindriven-examples/

-- Use Stackage LTS 24.2 as the main package source
import: https://www.stackage.org/lts-24.31/cabal.config

with-compiler: ghc-9.10.3
Loading