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.
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(notBandAggregate) - ✅
BandCreated(notBandCreatedEvent) - ✅
CreateBand(notCreateBandCommand)
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 registeredDanceHallGeocoded- Geographic coordinates were added
Commands:
CreateDanceHall- Register a new dance hallGeocodeDanceHall- Add geographic coordinates
Event
Represents a dance event (performance at a venue on a specific date).
Events:
EventCreated- A dance event was scheduledEventUpdated- Event details were modifiedEventDeleted- An event was cancelled/removed
Commands:
CreateEvent- Schedule a new dance eventUpdateEvent- Modify event detailsDeleteEvent- Cancel/remove an event
UserPreferences
Represents a user's preferences (origins, ignored items).
Events:
OriginAdded/OriginRemoved- User's home locationsBandIgnored/BandUnignored- Filtered bandsDanceHallIgnored/DanceHallUnignored- Filtered venuesCityIgnored/CityUnignored- Filtered citiesMunicipalityIgnored/MunicipalityUnignored- Filtered municipalitiesStateIgnored/StateUnignored- Filtered states
Commands:
AddOrigin/RemoveOriginIgnoreBand/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
- Command is created - User intention (e.g.,
CreateBand) - Command is validated - Business rules checked
- Event is produced - Command creates a domain event
- Event is stored - Persisted in event store
- Event is published - Sent to AMQP/RabbitMQ
- Event is applied - Aggregate state is updated
- 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:
- Event Store - Source of truth
- readview Package - Automatically updates projections
- Projection Tables - Traditional PostgreSQL tables for queries
See the dancefinder service for projection implementations.
Testing
Test aggregates by:
- Creating a sequence of events
- Applying them to an aggregate
- 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.