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