Skip to content

Commit

Permalink
db-repo, apiserver: allow string primary keys and added some generics;
Browse files Browse the repository at this point in the history
This commit changes the way you work with `Repository`s and CRUD
handlers. For repositories, we can now use strings as primary keys as
well (using `DistributedModel`s) and the methods to access entities are
now tied to the actual entities (giving you more type safety). The
repository now returns you the correct values directly, no need to pass
an empty value into the repository anymore.

The CRUD handlers are now also fully typed (so if you have `FooCreateInput`
as your create input, that is exactly the type you are getting in the
`TransformCreate` callback, no manual type casting needed). The
`GetCreateInput` and similar methods have also been removed as we can
now just create a zero value as needed.

** This is a breaking change. **
  • Loading branch information
ajscholl committed May 27, 2024
1 parent fa5f4eb commit 8028f7b
Show file tree
Hide file tree
Showing 77 changed files with 1,785 additions and 2,304 deletions.
55 changes: 18 additions & 37 deletions docs/docs/how-to/logging/src/log-context/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/justtrackio/gosoline/pkg/cfg"
db_repo "github.com/justtrackio/gosoline/pkg/db-repo"
"github.com/justtrackio/gosoline/pkg/httpserver/crud"
"github.com/justtrackio/gosoline/pkg/log"
"github.com/justtrackio/gosoline/pkg/mdl"
)
Expand Down Expand Up @@ -41,17 +40,17 @@ type UpdateInput struct {
type TodoCrudHandlerV0 struct {
// highlight-next-line
logger log.Logger
repo db_repo.Repository
repo db_repo.Repository[uint, *Todo]
}

// snippet-end: crud handler v0

// snippet-start: new todo crud handler
func NewTodoCrudHandler(ctx context.Context, config cfg.Config, logger log.Logger) (*TodoCrudHandlerV0, error) {
var err error
var repo db_repo.Repository
var repo db_repo.Repository[uint, *Todo]

if repo, err = db_repo.New(config, logger, settings); err != nil {
if repo, err = db_repo.New[uint, *Todo](config, logger, settings); err != nil {
return nil, fmt.Errorf("can not create db_repo.Repositorys: %w", err)
}

Expand All @@ -66,18 +65,10 @@ func NewTodoCrudHandler(ctx context.Context, config cfg.Config, logger log.Logge

// snippet-end: new todo crud handler

func (h TodoCrudHandlerV0) GetRepository() crud.Repository {
func (h TodoCrudHandlerV0) GetRepository() db_repo.Repository[uint, *Todo] {
return h.repo
}

func (h TodoCrudHandlerV0) GetModel() db_repo.ModelBased {
return &Todo{}
}

func (h TodoCrudHandlerV0) GetCreateInput() interface{} {
return &CreateInput{}
}

// snippet-start: truncate
func truncate(ctx context.Context, text string) string {
r := []rune(text)
Expand All @@ -97,57 +88,47 @@ func truncate(ctx context.Context, text string) string {
// snippet-end: truncate

// snippet-start: transform create
func (h TodoCrudHandlerV0) TransformCreate(ctx context.Context, input interface{}, model db_repo.ModelBased) error {
in := input.(*CreateInput)
m := model.(*Todo)

func (h TodoCrudHandlerV0) TransformCreate(ctx context.Context, in *CreateInput) (*Todo, error) {
// highlight-start
localctx := log.InitContext(ctx)
m.Text = truncate(localctx, in.Text)
// highlight-end
m.DueDate = in.DueDate
m := &Todo{
Text: truncate(localctx, in.Text),
// highlight-end
DueDate: in.DueDate,
}

return nil
return m, nil
}

// snippet-end: transform create

func (h TodoCrudHandlerV0) GetUpdateInput() interface{} {
return &UpdateInput{}
}

// snippet-start: transform update
func (h TodoCrudHandlerV0) TransformUpdate(ctx context.Context, input interface{}, model db_repo.ModelBased) error {
in := input.(*UpdateInput)
m := model.(*Todo)

func (h TodoCrudHandlerV0) TransformUpdate(ctx context.Context, in *UpdateInput, m *Todo) (*Todo, error) {
// highlight-start
localctx := log.InitContext(ctx)
m.Text = truncate(localctx, in.Text)
// highlight-end

return nil
return m, nil
}

// snippet-end: transform update

func (h TodoCrudHandlerV0) TransformOutput(ctx context.Context, model db_repo.ModelBased, apiView string) (interface{}, error) {
func (h TodoCrudHandlerV0) TransformOutput(ctx context.Context, model *Todo, apiView string) (*Todo, error) {
return model, nil
}

func (h TodoCrudHandlerV0) List(ctx context.Context, qb *db_repo.QueryBuilder, apiView string) (interface{}, error) {
func (h TodoCrudHandlerV0) List(ctx context.Context, qb *db_repo.QueryBuilder, apiView string) ([]*Todo, error) {
var err error

// Instatiate a list of Todo objects, called result.
result := make([]*Todo, 0)
var result []*Todo

// Query the database using a Context and a QueryBuilder. If it finds the results, it sets them on result. Otherwise, it returns an error.
if err = h.repo.Query(ctx, qb, &result); err != nil {
if result, err = h.repo.Query(ctx, qb); err != nil {
return nil, fmt.Errorf("can not query todo items: %w", err)
}

// Transform each result with TransformOutput().
out := make([]interface{}, len(result))
out := make([]*Todo, len(result))
for i, res := range result {
if out[i], err = h.TransformOutput(ctx, res, apiView); err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/how-to/logging/src/log-context/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func main() {
def := &httpserver.Definitions{}

var err error
var handler crud.Handler
var handler crud.Handler[CreateInput, UpdateInput, *Todo, uint, *Todo]

// Created a new CRUD handler.
if handler, err = NewTodoCrudHandler(ctx, config, logger); err != nil {
Expand Down
62 changes: 17 additions & 45 deletions docs/docs/quickstart/http-server/src/write-crud-sql-app/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

"github.com/justtrackio/gosoline/pkg/cfg"
db_repo "github.com/justtrackio/gosoline/pkg/db-repo"
"github.com/justtrackio/gosoline/pkg/httpserver/crud"
"github.com/justtrackio/gosoline/pkg/log"
"github.com/justtrackio/gosoline/pkg/mdl"
)
Expand Down Expand Up @@ -51,7 +50,7 @@ type UpdateInput struct {

// snippet-start: crud handler
type TodoCrudHandlerV0 struct {
repo db_repo.Repository
repo db_repo.Repository[uint, *Todo]
}

// snippet-end: crud handler
Expand All @@ -60,10 +59,10 @@ type TodoCrudHandlerV0 struct {
func NewTodoCrudHandler(ctx context.Context, config cfg.Config, logger log.Logger) (*TodoCrudHandlerV0, error) {
// Declare `error` and `repo` variables.
var err error
var repo db_repo.Repository
var repo db_repo.Repository[uint, *Todo]

// Try to create a new `Repository` given a configuration, a logger, and settings. If there is an error, you return it.
if repo, err = db_repo.New(config, logger, settings); err != nil {
if repo, err = db_repo.New[uint, *Todo](config, logger, settings); err != nil {
return nil, fmt.Errorf("can not create db_repo.Repositorys: %w", err)
}

Expand All @@ -79,79 +78,52 @@ func NewTodoCrudHandler(ctx context.Context, config cfg.Config, logger log.Logge
// snippet-end: todo constructor

// snippet-start: get repo
func (h TodoCrudHandlerV0) GetRepository() crud.Repository {
func (h TodoCrudHandlerV0) GetRepository() db_repo.Repository[uint, *Todo] {
return h.repo
}

// snippet-end: get repo

// snippet-start: get model
func (h TodoCrudHandlerV0) GetModel() db_repo.ModelBased {
return &Todo{}
}

// snippet-end: get model

// snippet-start: create input
func (h TodoCrudHandlerV0) GetCreateInput() interface{} {
return &CreateInput{}
}

// snippet-end: create input

// snippet-start: transform create
func (h TodoCrudHandlerV0) TransformCreate(ctx context.Context, input interface{}, model db_repo.ModelBased) error {
in := input.(*CreateInput)
m := model.(*Todo)

m.Text = in.Text
m.DueDate = in.DueDate
func (h TodoCrudHandlerV0) TransformCreate(ctx context.Context, in *CreateInput) (*Todo, error) {
m := &Todo{
Text: in.Text,
DueDate: in.DueDate,
}

return nil
return m, nil
}

// snippet-end: transform create

// snippet-start: get update input
func (h TodoCrudHandlerV0) GetUpdateInput() interface{} {
return &UpdateInput{}
}

// snippet-end: get update input

// snippet-start: transform update
func (h TodoCrudHandlerV0) TransformUpdate(ctx context.Context, input interface{}, model db_repo.ModelBased) error {
in := input.(*UpdateInput)
m := model.(*Todo)

func (h TodoCrudHandlerV0) TransformUpdate(ctx context.Context, in *UpdateInput, m *Todo) (*Todo, error) {
m.Text = in.Text

return nil
return m, nil
}

// snippet-end: transform update

// snippet-start: transform output
func (h TodoCrudHandlerV0) TransformOutput(ctx context.Context, model db_repo.ModelBased, apiView string) (interface{}, error) {
func (h TodoCrudHandlerV0) TransformOutput(ctx context.Context, model *Todo, apiView string) (*Todo, error) {
return model, nil
}

// snippet-end: transform output

// snippet-start: list
func (h TodoCrudHandlerV0) List(ctx context.Context, qb *db_repo.QueryBuilder, apiView string) (interface{}, error) {
func (h TodoCrudHandlerV0) List(ctx context.Context, qb *db_repo.QueryBuilder, apiView string) ([]*Todo, error) {
var err error

// Instatiate a list of Todo objects, called result.
result := make([]*Todo, 0)
var result []*Todo

// Query the database using a Context and a QueryBuilder. If it finds the results, it sets them on result. Otherwise, it returns an error.
if err = h.repo.Query(ctx, qb, &result); err != nil {
if result, err = h.repo.Query(ctx, qb); err != nil {
return nil, fmt.Errorf("can not query todo items: %w", err)
}

// Transform each result with TransformOutput().
out := make([]interface{}, len(result))
out := make([]*Todo, len(result))
for i, res := range result {
if out[i], err = h.TransformOutput(ctx, res, apiView); err != nil {
return nil, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func main() {
def := &httpserver.Definitions{}

var err error
var handler crud.Handler
var handler crud.Handler[CreateInput, UpdateInput, *Todo, uint, *Todo]

// Created a new CRUD handler.
if handler, err = NewTodoCrudHandler(ctx, config, logger); err != nil {
Expand Down
68 changes: 48 additions & 20 deletions examples/apiserver/simple-handlers/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package main

import (
"context"
"fmt"
"time"

db_repo "github.com/justtrackio/gosoline/pkg/db-repo"
"github.com/justtrackio/gosoline/pkg/db-repo"
)

type MyEntity struct {
Expand All @@ -19,6 +20,14 @@ func (e *MyEntity) GetId() *uint {
return &e.Id
}

func (e *MyEntity) GetUpdatedAt() *time.Time {
return e.UpdatedAt
}

func (e *MyEntity) GetCreatedAt() *time.Time {
return e.CreatedAt
}

func (e *MyEntity) SetUpdatedAt(updatedAt *time.Time) {
e.UpdatedAt = updatedAt
}
Expand All @@ -29,40 +38,59 @@ func (e *MyEntity) SetCreatedAt(createdAt *time.Time) {

type MyEntityRepository struct{}

func (*MyEntityRepository) Create(ctx context.Context, value db_repo.ModelBased) error {
func (*MyEntityRepository) Create(ctx context.Context, value *MyEntity) error {
return nil
}

func (*MyEntityRepository) Read(ctx context.Context, id *uint, out db_repo.ModelBased) error {
return nil
func (*MyEntityRepository) Read(ctx context.Context, id uint) (*MyEntity, error) {
if id == 1 {
return &MyEntity{
Id: 1,
Prop1: "text",
}, nil
}

if id == 2 {
return &MyEntity{
Id: 2,
Prop2: "text",
}, nil
}

return nil, db_repo.NewRecordNotFoundError(fmt.Sprintf("%d", id), "myEntity", fmt.Errorf("not found"))
}

func (*MyEntityRepository) Update(ctx context.Context, value db_repo.ModelBased) error {
func (*MyEntityRepository) Update(ctx context.Context, value *MyEntity) error {
return nil
}

func (*MyEntityRepository) Delete(ctx context.Context, value db_repo.ModelBased) error {
func (*MyEntityRepository) Delete(ctx context.Context, value *MyEntity) error {
return nil
}

func (*MyEntityRepository) Query(ctx context.Context, qb *db_repo.QueryBuilder, result interface{}) error {
r := result.(*[]*MyEntity)
func (*MyEntityRepository) Query(ctx context.Context, qb *db_repo.QueryBuilder) ([]*MyEntity, error) {
return []*MyEntity{
{
Id: 1,
Prop1: "text",
},
{
Id: 2,
Prop2: "text",
},
}, nil
}

*r = append(*r, &MyEntity{
Id: 1,
Prop1: "text",
})
*r = append(*r, &MyEntity{
Id: 2,
Prop1: "text",
})
result = r
func (*MyEntityRepository) Count(ctx context.Context, qb *db_repo.QueryBuilder) (int, error) {
return 2, nil
}

return nil
func (r *MyEntityRepository) GetModelId() string {
return "project.family.name.MyEntity"
}

func (*MyEntityRepository) Count(ctx context.Context, qb *db_repo.QueryBuilder, model db_repo.ModelBased) (int, error) {
return 2, nil
func (r *MyEntityRepository) GetModelName() string {
return "MyEntity"
}

func (*MyEntityRepository) GetMetadata() db_repo.Metadata {
Expand Down
Loading

0 comments on commit 8028f7b

Please sign in to comment.