Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gorm module #10 #14

Merged
merged 4 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

- Modules
- [Std Modules](modules/std.md)
- [Orm](modules/orm.md)
181 changes: 181 additions & 0 deletions docs/modules/orm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Orm

The Orm module is designed to help you easily set up and manage your gorm instance using configuration files. This module is highly modular and can be integrated seamlessly with other tools and services, such as Otel, NewRelic, etc. It is compatible with any SQL database. The module provides key components including orm.GormProvider, *gorm.DB, and tx.Transactioner, each serving a specific purpose to streamline database operations in your application.

## GormProvider

GormProvider is a utility that simplifies the process of retrieving a *gorm.DB instance. It is the preferred method for accessing the gorm.DB instance, rather than injecting it directly via a constructor.


The Get method in GormProvider checks the context for an existing transaction. If a transaction is found, it returns that specific instance. Otherwise, it provides a gorm.DB instance with the given context
This allows for consistent and context-aware access to your database, ensuring that operations respect transactional boundaries when necessary.
```golang
func (p GormProvider) Get(ctx context.Context) *gorm.DB {
if found := tx.From(ctx); found != nil {
return found
}
return p.db.WithContext(ctx)
}
```

## Transactioner
Transactioner is a utility struct designed to facilitate transaction management within gorm by utilizing context. This is particularly useful in services where multiple operations need to be executed as part of a single transaction.


### Example Usage

The following is an example of a service that uses Transactioner to manage a series of database operations within a single transaction:

```golang
type Service struct {
txr tx.Transactioner
repo Repository
}

func NewService(txr tx.Transactioner, repo Repositroy) *Service {
return &service{txr: txr, repo: repo}
}

func (s *service) Save(parentctx context.Context) error {
return s.txr.Transaction(parentctx, func(ctx context.Context) error {
if err := repo.SaveOne(ctx); err != nil {
return err
}

if err := repo.SaveTwo(ctx); err != nil {
return err
}

return nil
})
}
```
In this example, the Save method encapsulates multiple operations (SaveOne and SaveTwo) within a single transaction. If any operation fails, the entire transaction is rolled back, maintaining data integrity.


## Drivers
Drivers in this module provide an abstraction layer for different gorm or sql/db dialects, allowing the Orm module to work with various SQL databases. Each driver can have its own unique configuration and options, making it flexible to adapt to different database setups.


### Postgres
It is a `gorm` Postgres driver abstraction for Chaki

#### Usage
```golang
func main() {
app := chaki.New()

app.Use(
orm.Module(postgresdriver.New()),
)

app.Provide(
newRepository,
)
}


type Repository struct {
gp orm.GormProvider
}

func newRepository(gp orm.GormProvider) *Repository {
return &repository{
gp: gp,
}
}

func (repo *repository) someOperation(ctx context.Context) {
db := repo.gp.Get(ctx)
// you can use gorm instance
}

```

#### Config
```yaml
gorm:
host: "string" #required
port: "number" #required
username: "string" #required
password: "string" #required

```


## Repository

The Repository interface is an abstraction built on top of gorm, designed to minimize repetitive code for common database operations. This interface provides a standardized set of methods for interacting with the database, such as finding, saving, updating, and deleting records.

```go

type Repository[Id IdType, T any] interface {
FindById(ctx context.Context, id Id) (*T, error)
FindOne(ctx context.Context, q query.Query) (*T, error)
FindAll(ctx context.Context, q query.Query) ([]*T, error)
Update(ctx context.Context, q query.Query, update any) error
Save(ctx context.Context, t *T) (*T, error)
SaveAll(ctx context.Context, ts []*T) error
DeleteById(ctx context.Context, id Id) error
Delete(ctx context.Context, q query.Query) error
ListPageable(ctx context.Context, q query.Query, req PageableRequest) (*PageableResponse[*T], error)
ParseQuery(ctx context.Context, q query.Query) *gorm.DB
Context(ctx context.Context) *gorm.DB
}

```

### Usage
```golang
// Model
type Foo struct {
Id int `gorm:"primaryKey"`
Bar string
}

func NewRepository(gp orm.GormProvider) repository.Repository[int,Foo] {
return repository.New[int, Foo](gp)
}

type Service struct {
fooRepo repository.Repository[int,Foo]
}

func (s *service) GetFoo(ctx context.Context, id int) (*Foo, error) {
return s.fooRepo.FindById(ctx, id)
}

```

### With Composition
Composition allows you to extend the base repository with custom methods, giving you the flexibility to add specific queries or operations while still leveraging the core repository functionality.

```golang
type FooRepository struct {
repository.Repository[int, Foo]
}

func NewRepository(gp gorm.Provider) *FooRepository {
return &FooRepository{
Repository: repository.New[int, Foo]
}
}

// Custom repo function
func (repo *FooRepository) AdvenceQuery(ctx context.Context, id int) (*Foo, error) {
// you can acces all base class functionalities here
return repo.FindById(ctx, id)
}

func (repo *FooRepository) DirectGormQuery(ctx context.Context) error {
// you can get gorm instance here
gormdb := repo.Context(ctx)

// gorm operations...

return nil
}

```

In this case, the FooRepository struct extends the base repository and adds custom methods like AdvenceQuery and DirectGormQuery, providing additional functionality while maintaining access to the core repository methods.
5 changes: 5 additions & 0 deletions example/server-with-postgres/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
gorm:
host:
port:
username:
password:
93 changes: 93 additions & 0 deletions example/server-with-postgres/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"context"

"github.com/Trendyol/chaki"
"github.com/Trendyol/chaki/logger"
"github.com/Trendyol/chaki/modules/orm"
postgresdriver "github.com/Trendyol/chaki/modules/orm/driver/postgres"
"github.com/Trendyol/chaki/modules/otel"
otelserver "github.com/Trendyol/chaki/modules/otel/server"
"github.com/Trendyol/chaki/modules/server"
"github.com/Trendyol/chaki/modules/server/controller"
"github.com/Trendyol/chaki/modules/server/middlewares"
"github.com/Trendyol/chaki/modules/server/route"
"github.com/Trendyol/chaki/modules/swagger"
)

func main() {
app := chaki.New()

app.WithOption(
chaki.WithConfigPath("config.yaml"),
)

app.Use(
otel.Module(),
orm.Module(postgresdriver.New()),
otelserver.Module(),
server.Module(),
swagger.Module(),
)

app.Provide(
middlewares.ErrHandler,

newFooRepository,
newHelloController,
)

if err := app.Start(); err != nil {
logger.Fatal(err)
}
}

type getFooReq struct {
Id int `query:"id"`
}

type HelloController struct {
*controller.Base
repo *fooRepository
}

func newHelloController(repo *fooRepository) controller.Controller {
return &HelloController{
Base: controller.New("Hello Controller").SetPrefix("/hello"),
repo: repo,
}
}

func (c *HelloController) Routes() []route.Route {
return []route.Route{
route.Get("/", c.getFoo).Name("Get Foo"),
}
}

func (c *HelloController) getFoo(ctx context.Context, req getFooReq) (*foo, error) {
logger.From(ctx).Info("trace id and spand id will be logged wiht message")
return c.repo.getFoo(ctx, req.Id)
}

type foo struct {
Id int `gorm:"primaryKey"`
Bar string `gorm:"bar"`
}

type fooRepository struct {
gp orm.GormProvider
}

func newFooRepository(gp orm.GormProvider) *fooRepository {
return &fooRepository{gp}
}

func (repo *fooRepository) getFoo(ctx context.Context, id int) (*foo, error) {
f := &foo{}
err := repo.gp.Get(ctx).Where("id = ?", id).Find(f).Error
if err != nil {
return nil, err
}
return f, nil
}
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21
toolchain go1.21.1

require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/Trendyol/kafka-konsumer/v2 v2.3.3
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5
github.com/go-playground/validator/v10 v10.22.0
Expand All @@ -23,6 +24,8 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/sync v0.6.0
gorm.io/driver/postgres v1.5.9
gorm.io/gorm v1.25.11
)

require (
Expand All @@ -43,6 +46,12 @@ require (
github.com/gofiber/adaptor/v2 v2.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
Expand Down
21 changes: 21 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Trendyol/kafka-cronsumer v1.5.3 h1:I3x7KUceHlae69MyBYx6Vj1ctMexeIKEUq2xNg0wvG8=
github.com/Trendyol/kafka-cronsumer v1.5.3/go.mod h1:VpweJmKY+6dppFhzWOZDbZfxBNuJkSxB12CcuZWBNFU=
github.com/Trendyol/kafka-konsumer/v2 v2.3.3 h1:9l4ODhLkn7/MjiJzrItakAgz0ZbcW9fSb6I1I1oH8Kw=
Expand Down Expand Up @@ -60,8 +62,21 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
Expand Down Expand Up @@ -130,6 +145,8 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
Expand Down Expand Up @@ -244,3 +261,7 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
10 changes: 10 additions & 0 deletions modules/orm/driver/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package driver

import (
"github.com/Trendyol/chaki/config"
"gorm.io/gorm"
)

type Driver interface {
BuildGorm(cfg *config.Config) (*gorm.DB, error)
}
Loading
Loading