Files
dancefinder/domain/README.md
T
argoyle e84c73e6d7 feat: add new domain model for band and update README
Add a new projection for handling the creation of bands 
with support for upserts in the database. Update the 
README to document the DanceFinder domain package's 
architecture and naming conventions, aligning with 
Domain-Driven Design and Event Sourcing principles. 
New dependencies are included for database testing and 
event sourcing functionalities.
refactor(schema): rename UUID columns to standard IDs

Simplify database schema by removing dual identity system 
and standardizing column names. All tables now use a single 
UUID-type `id` column, with foreign keys renamed to `band_id` 
and `dance_hall_id`, enhancing clarity and consistency in 
identifying records. Update all related code to reflect these 
changes, improving developer experience and maintaining data 
integrity throughout the migration.
feat: add truncate functionality for projection tables

Add functions to truncate the band, dance_hall, event, and user 
preference tables to enable rebuilding projections from events. 
Eliminate foreign key constraints between read view tables to 
ensure referential integrity is maintained at the write side and 
allow independent resets of read models. Include documentation 
explaining the architectural decision against foreign key usage 
within the system.
feat(migrations): rename UUID columns to id and simplify schema

Update database schema by renaming UUID columns to 'id' across 
event, band, dance_hall, ignored_band, and ignored_dance_hall 
tables. Drop old numeric IDs and foreign key constraints to 
streamline the schema, ensuring UUIDs serve as primary 
identifiers. Recreate necessary foreign key constraints with 
the updated column names to maintain referential integrity.
feat(database): replace integer IDs with UUIDs for events

Update event and associated entities to use UUIDs instead of 
integer IDs. Modify SQL queries and data structures to ensure 
band and dance hall identifiers are UUIDs, enhancing the 
system's scalability and integration capabilities.
2025-11-04 11:27:51 +01:00

5.0 KiB

DanceFinder Domain Package

This package contains the core domain model for the DanceFinder application, following Domain-Driven Design (DDD) and Event Sourcing principles.

Architecture

The domain package uses the eventsourced framework to implement:

  • Aggregates - Domain entities that are event-sourced
  • Domain Events - Immutable facts about what happened
  • Commands - Intentions to change state

Note: Repositories are located in the separate repositories package.

Naming Conventions

Following DDD best practices, we do not use suffixes like Aggregate or Event:

  • Band (not BandAggregate)
  • BandCreated (not BandCreatedEvent)
  • CreateBand (not CreateBandCommand)

The domain model is self-documenting through types and context.

Aggregates

Band

Represents a musical band/performer.

Events:

  • BandCreated - A band was registered in the system

Commands:

  • CreateBand - Register a new band

DanceHall

Represents a venue where dance events occur.

Events:

  • DanceHallCreated - A dance hall was registered
  • DanceHallGeocoded - Geographic coordinates were added

Commands:

  • CreateDanceHall - Register a new dance hall
  • GeocodeDanceHall - Add geographic coordinates

Event

Represents a dance event (performance at a venue on a specific date).

Events:

  • EventCreated - A dance event was scheduled
  • EventUpdated - Event details were modified
  • EventDeleted - An event was cancelled/removed

Commands:

  • CreateEvent - Schedule a new dance event
  • UpdateEvent - Modify event details
  • DeleteEvent - Cancel/remove an event

UserPreferences

Represents a user's preferences (origins, ignored items).

Events:

  • OriginAdded / OriginRemoved - User's home locations
  • BandIgnored / BandUnignored - Filtered bands
  • DanceHallIgnored / DanceHallUnignored - Filtered venues
  • CityIgnored / CityUnignored - Filtered cities
  • MunicipalityIgnored / MunicipalityUnignored - Filtered municipalities
  • StateIgnored / StateUnignored - Filtered states

Commands:

  • AddOrigin / RemoveOrigin
  • IgnoreBand / UnignoreBand
  • (similar for DanceHall, City, Municipality, State)

Usage Example

package main

import (
    "context"
    "gitlab.com/unboundsoftware/dancefinder/domain"
    "gitlab.com/unboundsoftware/dancefinder/repositories"
    "gitlab.com/unboundsoftware/eventsourced/eventsourced"
    "gitlab.com/unboundsoftware/eventsourced/pg"
    "gitlab.com/unboundsoftware/eventsourced/amqp"
)

func main() {
    ctx := context.Background()

    // Setup event store
    eventStore, _ := pg.New(ctx, db,
        pg.WithEventTypes(
            &domain.BandCreated{},
            &domain.DanceHallCreated{},
            &domain.DanceHallGeocoded{},
            &domain.EventCreated{},
            &domain.EventUpdated{},
            &domain.EventDeleted{},
            &domain.OriginAdded{},
            &domain.OriginRemoved{},
            &domain.BandIgnored{},
            &domain.BandUnignored{},
            // ... all event types
        ),
    )

    // Setup event publisher
    amqpConn, _ := amqp.New(rabbitMQConn)

    // Create repositories
    bandRepo := repositories.NewBandRepository(eventStore, amqpConn)

    // Create a band using a command
    cmd := &domain.CreateBand{
        Name: "Lasse Stefanz",
    }

    band, err := bandRepo.Create(ctx, cmd)
    if err != nil {
        panic(err)
    }

    // Load the band later
    loaded, err := bandRepo.Load(ctx, band.Identity())
    if err != nil {
        panic(err)
    }
}

Event Flow

  1. Command is created - User intention (e.g., CreateBand)
  2. Command is validated - Business rules checked
  3. Event is produced - Command creates a domain event
  4. Event is stored - Persisted in event store
  5. Event is published - Sent to AMQP/RabbitMQ
  6. Event is applied - Aggregate state is updated
  7. Projections updated - Read models are updated via readview package

Integration with Read Models

The domain package focuses on write models (commands and events). Read models (queries) are handled by:

  1. Event Store - Source of truth
  2. readview Package - Automatically updates projections
  3. Projection Tables - Traditional PostgreSQL tables for queries

See the dancefinder service for projection implementations.

Testing

Test aggregates by:

  1. Creating a sequence of events
  2. Applying them to an aggregate
  3. Asserting the resulting state
func TestBandCreation(t *testing.T) {
    band := &domain.Band{}

    event := &domain.BandCreated{
        BaseEvent: eventsourced.BaseEvent{
            EventAggregateId: "band-123",
            EventTime:        time.Now(),
        },
        Name: "Lasse Stefanz",
    }

    err := band.Apply(event)
    require.NoError(t, err)

    assert.Equal(t, "Lasse Stefanz", band.Name)
    assert.Equal(t, "band-123", band.Identity())
}

Migration Strategy

See MIGRATION.md for the complete migration strategy from the current CRUD-based system to this event-sourced domain model.