The entity manager is an implementation of the Facade pattern based on rel.Changeset
.
All entities have 3 states:
new
: base state for new entity - not managed by the entity manager.
managed
: entity in new
state passed to the Persist
method of the entity manager or loaded from the database using method from the entity manager like Find
removed
: Entity must by removed after flush
Method Flush
- commits changes for all managed
entities in transaction by rel.Changeset
, and reset rel.Changeset
state.
Additionally:
Find
method with id criteria (find by id) should check if entity is already loaded and return it if is true (do not make a new request)
- Transactional write-behind allows to replace many
Insert
in code to one InsertAll
- Lazy users can wrap in middleware with automatic call
flush
- Use one time
ctx context.Conext
API
Persist(record interface{})
Make an entity managed and persistent.
The record will be inserted into the database as a result of the Flush
operation.
Only for new entities
Remove(record interface{})
Removes an entity.
A removed entity will be removed from the database as a result of the Flush
operation.
Refresh(ctx context.Context, record interface{}) error
Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been persisted.
Flush() error
Flushes all changes to an entities that have been queued up to now to the database.
Select methods from rel.Repository
Retrieve the records, persist them in the entity manager, and return to the user.
API may by changed like:
Find[T any](ctx context.Context, queriers ...Querier) (*T, error)
Example
Current way:
func (t Todos) MoveToBasket(ctx context.Context, repo rel.Repository, id int) {
var todo todos.Todo
if err := repo.Find(ctx, &todo, where.Eq("id", id)); err != nil {
panic(err)
}
changesTodo = rel.NewChangeset(&todo)
var basket todos.Category
if err := repo.Find(ctx, &basket, where.Eq("name", "basket")); err != nil {
panic(err)
}
changesBasket = rel.NewChangeset(&basket)
// domain code
todo.Category = basket
basket.Total += 1
repo.Transaction(ctx, func(ctx context.Context) error {
err := repo.Update(c, &todo, changesTodo)
if err != nil {
return err
}
err := repo.Update(c, &basket, changesBasket)
if err != nil {
return err
}
return nil
})
if err != nil {
render(ctx, "Server error", 500)
}
render(ctx, todo, 200)
}
If you don't want to get a problems, you should always call Update
in the same method where entity fetched. Very often it turns out the controller. Very often this place is the controller
Entity manager:
func (t Todos) DoSomething(ctx context.Context, em rel.EntityManager, id int) {
var todo todos.Todo
if err := em.Find(ctx, &todo, where.Eq("id", id)); err != nil {
panic(err)
}
var basket todos.Category
if err := em.Find(ctx, &basket, where.Eq("name", "basket")); err != nil {
panic(err)
}
// domain code
todo.Category = basket
basket.Total += 1
render(ctx, todo, 200)
}
func MiddlewareForLazyUsers(ctx iris.Context, repo rel.Repository) {
em := rel.NewEntityManager(repo)
// request dependency
ctx.RegisterDependency(em)
ctx.Next()
err := em.Flush(ctx.Context())
if err != nil {
render(ctx, "Server error", 500)
}
}
Improvements
- Dedicated repositories for entity, this will improve the user experience and allow use of generics:
rel.Repository
: Universal repository(current implementation)
rel.EntityRepository[T any]
: Repository for entity. Similar to rel.Repository
, but with generics and managed by entity manager.
rel.EntityManager
: Entity manager with methods:
Persist(record interface{})
Remove(record interface{})
Refresh(ctx context.Context, record interface{}) error
Flush() error
repository(...) any
: internal method for rel.Repository[T any]
.
func (t Todos) DoSomething(ctx context.Context, em rel.EntityManager, id int) {
todo, err := rel.Repository[todos.Todo](em).Find(ctx, where.Eq("id", id))
if err != nil {
panic(err)
}
basket, err := rel.Repository[todos.Category](em).Find(ctx, where.Eq("name", "basket"))
if err != nil {
panic(err)
}
// domain code
todo.Category = basket
basket.Total += 1
render(ctx, todo, 200)
}
- User can implement own repository and register than in entity manager.
- Association managed by entity manager
when the primary key (id) is not zero.
does not work for client side id (like UUID). Entity manager resolve this issue.
Summary
Entity Manager allows you to keep the domain clean, and removes the complexity of working with the database