Skip to content

Commit dadbc02

Browse files
authored
Merge pull request #31 from subomi/subomi/feat/context-and-dynamic-type-analysis
2 parents cde8343 + 405fa50 commit dadbc02

9 files changed

Lines changed: 1752 additions & 114 deletions

File tree

docs/bulk_registration.md

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Bulk Registration API Design
2+
3+
**Status:** Proposed
4+
**Author:** Subomi Oluwalana
5+
**Date:** January 2026
6+
7+
---
8+
9+
## Table of Contents
10+
11+
1. [Overview](#overview)
12+
2. [Problem](#problem)
13+
3. [Solution](#solution)
14+
4. [API Design](#api-design)
15+
5. [Internal Implementation](#internal-implementation)
16+
6. [Usage Examples](#usage-examples)
17+
7. [Error Handling](#error-handling)
18+
8. [Design Decisions](#design-decisions)
19+
9. [Implementation Plan](#implementation-plan)
20+
21+
---
22+
23+
## Overview
24+
25+
This document describes a cleaner way to register multiple type migrations for a single version atomically.
26+
27+
---
28+
29+
## Problem
30+
31+
Currently, registering multiple migrations for a single version requires repetitive calls:
32+
33+
```go
34+
rms.Register[User](rm, "2024-01-01", &UserMigration{})
35+
rms.Register[Address](rm, "2024-01-01", &AddressMigration{})
36+
rms.Register[Order](rm, "2024-01-01", &OrderMigration{})
37+
rms.Register[Payment](rm, "2024-01-01", &PaymentMigration{})
38+
```
39+
40+
This is verbose and error-prone (easy to typo the version string).
41+
42+
---
43+
44+
## Solution: Struct-Based Bulk Registration
45+
46+
Provide a `RegisterVersion` function that accepts a structured definition of all migrations for a version.
47+
48+
---
49+
50+
## API Design
51+
52+
### TypedMigration Struct
53+
54+
```go
55+
// TypedMigration pairs a Go type with its migration implementation.
56+
// Used with RegisterVersion for bulk registration.
57+
type TypedMigration struct {
58+
// Type is an instance of the Go type this migration handles.
59+
// Only the type information is used; the value is ignored.
60+
// Example: User{} or (*User)(nil)
61+
Type any
62+
63+
// Migration is the TypeMigration implementation for this type.
64+
Migration TypeMigration
65+
}
66+
```
67+
68+
### VersionMigrations Struct
69+
70+
```go
71+
// VersionMigrations defines all type migrations for a specific API version.
72+
type VersionMigrations struct {
73+
// Version is the API version string (e.g., "2024-01-01" or "v2.0.0").
74+
Version string
75+
76+
// Migrations is the list of type migrations for this version.
77+
Migrations []TypedMigration
78+
}
79+
```
80+
81+
### RegisterVersion Function
82+
83+
```go
84+
// RegisterVersion registers all type migrations for a specific version atomically.
85+
// If any migration fails to register, no migrations are registered and an error is returned.
86+
//
87+
// Example:
88+
//
89+
// err := rms.RegisterVersion(rm, &rms.VersionMigrations{
90+
// Version: "2024-01-01",
91+
// Migrations: []rms.TypedMigration{
92+
// {Type: User{}, Migration: &UserMigration{}},
93+
// {Type: Address{}, Migration: &AddressMigration{}},
94+
// {Type: Order{}, Migration: &OrderMigration{}},
95+
// },
96+
// })
97+
//
98+
func RegisterVersion(rm *RequestMigration, vm *VersionMigrations) error
99+
```
100+
101+
---
102+
103+
## Internal Implementation
104+
105+
```go
106+
func RegisterVersion(rm *RequestMigration, vm *VersionMigrations) error {
107+
if vm == nil {
108+
return errors.New("version migrations cannot be nil")
109+
}
110+
111+
if vm.Version == "" {
112+
return errors.New("version cannot be empty")
113+
}
114+
115+
if len(vm.Migrations) == 0 {
116+
return errors.New("migrations list cannot be empty")
117+
}
118+
119+
// Validate all migrations first (atomic: fail before any registration)
120+
types := make([]reflect.Type, len(vm.Migrations))
121+
for i, tm := range vm.Migrations {
122+
if tm.Type == nil {
123+
return fmt.Errorf("migration %d: type cannot be nil", i)
124+
}
125+
if tm.Migration == nil {
126+
return fmt.Errorf("migration %d: migration cannot be nil", i)
127+
}
128+
types[i] = reflect.TypeOf(tm.Type)
129+
}
130+
131+
// Check for duplicate types within the same registration
132+
seen := make(map[reflect.Type]bool)
133+
for i, t := range types {
134+
if seen[t] {
135+
return fmt.Errorf("migration %d: duplicate type %v", i, t)
136+
}
137+
seen[t] = true
138+
}
139+
140+
// All validations passed - register atomically
141+
rm.mu.Lock()
142+
defer rm.mu.Unlock()
143+
144+
for i, tm := range vm.Migrations {
145+
// Use internal registration (already holds lock)
146+
if err := rm.registerTypeMigrationLocked(vm.Version, types[i], tm.Migration); err != nil {
147+
// Rollback: remove any migrations we just added for this version
148+
for j := 0; j < i; j++ {
149+
rm.unregisterTypeMigrationLocked(vm.Version, types[j])
150+
}
151+
return fmt.Errorf("migration %d (%v): %w", i, types[i], err)
152+
}
153+
}
154+
155+
return nil
156+
}
157+
```
158+
159+
---
160+
161+
## Usage Examples
162+
163+
### Before (Verbose)
164+
165+
```go
166+
rms.Register[User](rm, "2024-01-01", &UserMigration{})
167+
rms.Register[Address](rm, "2024-01-01", &AddressMigration{})
168+
rms.Register[Order](rm, "2024-01-01", &OrderMigration{})
169+
rms.Register[Payment](rm, "2024-01-01", &PaymentMigration{})
170+
171+
rms.Register[User](rm, "2024-06-01", &UserMigrationV2{})
172+
rms.Register[Workspace](rm, "2024-06-01", &WorkspaceMigration{})
173+
```
174+
175+
### After (Clean)
176+
177+
```go
178+
err := rms.RegisterVersion(rm, &rms.VersionMigrations{
179+
Version: "2024-01-01",
180+
Migrations: []rms.TypedMigration{
181+
{Type: User{}, Migration: &UserMigration{}},
182+
{Type: Address{}, Migration: &AddressMigration{}},
183+
{Type: Order{}, Migration: &OrderMigration{}},
184+
{Type: Payment{}, Migration: &PaymentMigration{}},
185+
},
186+
})
187+
if err != nil {
188+
log.Fatal(err)
189+
}
190+
191+
err = rms.RegisterVersion(rm, &rms.VersionMigrations{
192+
Version: "2024-06-01",
193+
Migrations: []rms.TypedMigration{
194+
{Type: User{}, Migration: &UserMigrationV2{}},
195+
{Type: Workspace{}, Migration: &WorkspaceMigration{}},
196+
},
197+
})
198+
if err != nil {
199+
log.Fatal(err)
200+
}
201+
```
202+
203+
---
204+
205+
## Error Handling
206+
207+
The function validates all migrations before registering any:
208+
209+
```go
210+
// This will fail atomically - no partial registration
211+
err := rms.RegisterVersion(rm, &rms.VersionMigrations{
212+
Version: "2024-01-01",
213+
Migrations: []rms.TypedMigration{
214+
{Type: User{}, Migration: &UserMigration{}}, // Valid
215+
{Type: nil, Migration: &AddressMigration{}}, // Invalid: nil type
216+
{Type: Order{}, Migration: &OrderMigration{}}, // Never reached
217+
},
218+
})
219+
// err: "migration 1: type cannot be nil"
220+
// No migrations registered
221+
```
222+
223+
---
224+
225+
## Design Decisions
226+
227+
| Decision | Choice | Rationale |
228+
|----------|--------|-----------|
229+
| API style | Struct-based | Clear, self-documenting, extensible |
230+
| Type specification | `Type any` with instance | Simpler than generics for bulk ops |
231+
| Atomicity | All-or-nothing | Prevents inconsistent state |
232+
| Validation order | Validate all → Register all | Fail fast, clean rollback |
233+
234+
---
235+
236+
## Implementation Plan
237+
238+
1. Add `TypedMigration` struct
239+
2. Add `VersionMigrations` struct
240+
3. Add internal `registerTypeMigrationLocked()` (assumes lock held)
241+
4. Add internal `unregisterTypeMigrationLocked()` for rollback
242+
5. Implement `RegisterVersion()` with validation and atomic registration
243+
6. Add tests for successful bulk registration
244+
7. Add tests for atomic failure (rollback on error)
245+
8. Add tests for duplicate type detection
246+
9. Update examples to use bulk registration
247+
248+
---
249+
250+
## Open Questions
251+
252+
1. Should `RegisterVersion` support registering the same type multiple times for different versions in a single call? (Current design: no, use separate calls)
253+
254+
2. Should we add a `RegisterVersions` (plural) function that accepts multiple `VersionMigrations` for even more bulk registration?

0 commit comments

Comments
 (0)