Moves resolver implementation from the pkg to the graph package for better organization. Updates gqlgen.yml configuration to align with the new structure and ensures that models point to the correct locations. This change improves maintainability and clarity of the codebase.
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.