build
This commit is contained in:
parent
02853c2957
commit
0f9b15c2c5
2 changed files with 0 additions and 124 deletions
BIN
dendrite
BIN
dendrite
Binary file not shown.
|
|
@ -1,124 +0,0 @@
|
||||||
# Unit of Work — Refactor Notes
|
|
||||||
|
|
||||||
## Current Issues
|
|
||||||
|
|
||||||
### 1. Lifetime
|
|
||||||
`UnitOfWork` is created once in the handler constructor and reused across every `Handle` call.
|
|
||||||
It should be **per-request**, created inside `Handle`.
|
|
||||||
|
|
||||||
### 2. Transaction leaks out
|
|
||||||
`Begin()` returns `*sql.Tx`, which is then passed manually to each repo constructor.
|
|
||||||
The UoW should own that wiring — callers should never touch the transaction directly.
|
|
||||||
|
|
||||||
### 3. Eager instantiation (rejected solution)
|
|
||||||
Having UoW create all repos in `Begin()` solves the leaking transaction, but forces every handler
|
|
||||||
to pay for repos it doesn't need. Not acceptable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Solution: Lazy Initialization
|
|
||||||
|
|
||||||
UoW exposes accessor methods that initialize each repo on first access, wired to the internal transaction.
|
|
||||||
Unused repos are never instantiated.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type UnitOfWork struct {
|
|
||||||
FileStore *store.FileStore
|
|
||||||
tx *sql.Tx
|
|
||||||
tags ITagRepository
|
|
||||||
notes INoteRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnitOfWork() *UnitOfWork {
|
|
||||||
return &UnitOfWork{FileStore: store.NewFileStore()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UnitOfWork) Begin() error {
|
|
||||||
tx, err := persistence.GetDBContext().DB.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.tx = tx
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UnitOfWork) Tags() ITagRepository {
|
|
||||||
if u.tags == nil {
|
|
||||||
u.tags = NewTagRepository(u.tx)
|
|
||||||
}
|
|
||||||
return u.tags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UnitOfWork) Notes() INoteRepository {
|
|
||||||
if u.notes == nil {
|
|
||||||
u.notes = NewNoteRepository(u.tx)
|
|
||||||
}
|
|
||||||
return u.notes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UnitOfWork) Commit() error {
|
|
||||||
if err := u.FileStore.Flush(); err != nil {
|
|
||||||
u.tx.Rollback()
|
|
||||||
u.tx = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := u.tx.Commit(); err != nil {
|
|
||||||
u.FileStore.Rollback()
|
|
||||||
u.tx = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u.tx = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *UnitOfWork) Rollback() {
|
|
||||||
if u.tx == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
u.tx.Rollback()
|
|
||||||
u.FileStore.Rollback()
|
|
||||||
u.tx = nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Usage in a Handler
|
|
||||||
|
|
||||||
```go
|
|
||||||
func (h *CreateNoteHandler) Handle(ctx context.Context, raw json.RawMessage) (any, error) {
|
|
||||||
var cmd createNoteCommand
|
|
||||||
if err := json.Unmarshal(raw, &cmd); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
uow := repositories.NewUnitOfWork()
|
|
||||||
if err := uow.Begin(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer uow.Rollback()
|
|
||||||
|
|
||||||
// Notes() is never called — never instantiated
|
|
||||||
dbTags, err := uow.Tags().GetByNames(ctx, tags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
uow.FileStore.Stage(cmd.Path, data)
|
|
||||||
return cmd.Path, uow.Commit()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Properties of This Design
|
|
||||||
|
|
||||||
| Property | Result |
|
|
||||||
|---|---|
|
|
||||||
| Transaction leaks out of UoW | No — `tx` is private |
|
|
||||||
| Repos instantiated when not needed | No — lazy on first access |
|
|
||||||
| Handler controls which repos it uses | Yes — only accessed repos are created |
|
|
||||||
| UoW created per-request | Yes — inside `Handle`, not constructor |
|
|
||||||
| Handler has no persistent state | Yes — can be zero-value struct |
|
|
||||||
Loading…
Add table
Reference in a new issue