Skip to content

Commit

Permalink
Merge pull request #2 from tonybka/feature/tonybka/sample-custom-gorm…
Browse files Browse the repository at this point in the history
…-type-uuidv1

Add base gorm model with customised ID value type
  • Loading branch information
tonybka authored Dec 19, 2022
2 parents c6976d8 + 890f35f commit 4e9f855
Show file tree
Hide file tree
Showing 15 changed files with 296 additions and 10 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
GO_CMD=go
GO_TEST=$(GO_CMD) test -count=1 -v -cover


.PHONY:test
test:
@-$(GO_TEST) ./... ||:
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Fundamental DDD elements in Golang
Fundamental elements of DDD implementation in Golang microservice




# Components
## Domain Layer
- Entity
- Value Object
- Domain Event

## Persistence Layer
- Data Model
- Repository
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package entity

import "github.com/tonybka/go-base-ddd/event"
import "github.com/tonybka/go-base-ddd/domain/event"

type BaseAggregateRoot struct {
BaseEntity
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 18 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,21 @@ module github.com/tonybka/go-base-ddd

go 1.18

require github.com/google/uuid v1.3.0
require (
github.com/google/uuid v1.3.0
gorm.io/gorm v1.24.2
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/stretchr/testify v1.8.1
gorm.io/driver/sqlite v1.4.3
)
29 changes: 29 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package gorm
package customgorm

import (
"database/sql/driver"
"errors"
"fmt"

"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)

type CustomTypeUUIDv1 uuid.UUID

// NewCustomTypeUUIDv1FromString -> parse string to CustomTypeUUIDv1
func NewCustomTypeUUIDv1FromString(s string) (CustomTypeUUIDv1, error) {
id, err := uuid.Parse(s)
return CustomTypeUUIDv1(id), err
// CustomTypeUUIDv1FromString -> parse string to CustomTypeUUIDv1
func CustomTypeUUIDv1FromString(s string) CustomTypeUUIDv1 {
return CustomTypeUUIDv1(uuid.MustParse(s))
}

//String -> String Representation of Binary16
Expand All @@ -24,6 +27,18 @@ func (my CustomTypeUUIDv1) GormDataType() string {
return "binary(16)"
}

// GormDBDataType returns gorm DB data type based on the current using database.
func (my CustomTypeUUIDv1) GormDBDataType(db *gorm.DB, field *schema.Field) string {
switch db.Dialector.Name() {
case "mysql":
return "BINARY(16)"
case "sqlite":
return "BLOB"
default:
return ""
}
}

func (my CustomTypeUUIDv1) MarshalJSON() ([]byte, error) {
s := uuid.UUID(my)
str := "\"" + s.String() + "\""
Expand All @@ -39,9 +54,13 @@ func (my *CustomTypeUUIDv1) UnmarshalJSON(by []byte) error {
// Scan --> tells GORM how to receive from the database
func (my *CustomTypeUUIDv1) Scan(value interface{}) error {

bytes, _ := value.([]byte)
parseByte, err := uuid.FromBytes(bytes)
*my = CustomTypeUUIDv1(parseByte)
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to decode value:", value))
}

parseBytes, err := uuid.FromBytes(bytes)
*my = CustomTypeUUIDv1(parseBytes)
return err
}

Expand Down
15 changes: 15 additions & 0 deletions infrastructure/persistence/base_model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package persistence

import (
"time"

customgorm "github.com/tonybka/go-base-ddd/infrastructure/custom_gorm"
"gorm.io/gorm"
)

type BaseModel struct {
ID customgorm.CustomTypeUUIDv1 `gorm:"primarykey;default:(UUID_TO_BIN(UUID()));"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
59 changes: 59 additions & 0 deletions infrastructure/tests/sqlite_db_connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package tests

import (
"io/ioutil"
"os"
"path"

"gorm.io/driver/sqlite"
"gorm.io/gorm"
)

type SqliteDBConnect struct {
dataDir string
dbConnection *gorm.DB
}

func NewSqliteDBConnect() (*SqliteDBConnect, error) {
var tempDir = ""

// Temp file setup
tempDir, err := ioutil.TempDir("", "tests-")
if err != nil {
return nil, err
}

// Database setup
tempDir = path.Join(tempDir, "test.sqlite3")
dialector := sqlite.Open(tempDir)

dbConn, err := gorm.Open(dialector, &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
return nil, err
}

sqliteDB, err := dbConn.DB()
if err != nil {
return nil, err
}
sqliteDB.SetMaxOpenConns(1)
dbConn.Debug()

return &SqliteDBConnect{dataDir: tempDir, dbConnection: dbConn}, nil
}

func (conn *SqliteDBConnect) Connection() *gorm.DB {
return conn.dbConnection
}

func (conn *SqliteDBConnect) CleanUp() error {
var err error

if len(conn.dataDir) > 0 {
err = os.RemoveAll(conn.dataDir)
}

return err
}
5 changes: 5 additions & 0 deletions samples/entity/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package entity

// Account is an user account as sample if entity
type Account struct {
}
11 changes: 11 additions & 0 deletions samples/persistence/account/account_model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package account

import (
"github.com/tonybka/go-base-ddd/infrastructure/persistence"
)

type AccountModel struct {
persistence.BaseModel

AccountName string `gorm:"column:account_name;unique"`
}
42 changes: 42 additions & 0 deletions samples/persistence/account/account_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package account

import (
"github.com/google/uuid"
customgorm "github.com/tonybka/go-base-ddd/infrastructure/custom_gorm"
"gorm.io/gorm"
)

type AccountRepository struct {
db *gorm.DB
}

func NewAccountRepository(db *gorm.DB) *AccountRepository {
return &AccountRepository{db}
}

func (repo *AccountRepository) Create(dataModel AccountModel) error {
if result := repo.db.Create(&dataModel); result.Error != nil {
return result.Error
}
return nil
}

func (repo *AccountRepository) FindById(id uuid.UUID) (AccountModel, error) {
var dataModel AccountModel

if result := repo.db.Where("id = ?", customgorm.CustomTypeUUIDv1FromString(id.String())).First(&dataModel); result.Error != nil {
return AccountModel{}, result.Error
}

return dataModel, nil
}

func (repo *AccountRepository) GetAll() ([]AccountModel, error) {
var dataModels []AccountModel

if result := repo.db.Find(&dataModels); result.Error != nil {
return nil, result.Error
}

return dataModels, nil
}
67 changes: 67 additions & 0 deletions samples/persistence/account/account_repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package account

import (
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
customgorm "github.com/tonybka/go-base-ddd/infrastructure/custom_gorm"
"github.com/tonybka/go-base-ddd/infrastructure/persistence"
"github.com/tonybka/go-base-ddd/infrastructure/tests"
"gorm.io/gorm"
)

type AccountRepositoryTestSuite struct {
suite.Suite

sqliteConnect *tests.SqliteDBConnect
dbConn *gorm.DB

accountRepo *AccountRepository
}

func (ts *AccountRepositoryTestSuite) SetupSuite() {
sqliteConn, err := tests.NewSqliteDBConnect()
require.NoError(ts.T(), err)

ts.sqliteConnect = sqliteConn
ts.dbConn = sqliteConn.Connection()

ts.dbConn.AutoMigrate(&AccountModel{})

ts.accountRepo = NewAccountRepository(ts.dbConn)
}

func (ts *AccountRepositoryTestSuite) TestCreateAccount() {
entityId := customgorm.CustomTypeUUIDv1FromString(uuid.New().String())

account := AccountModel{
BaseModel: persistence.BaseModel{
ID: entityId,
},
AccountName: "abc",
}

err := ts.accountRepo.Create(account)
ts.NoError(err)

all, err := ts.accountRepo.GetAll()
ts.NoError(err)
ts.Greater(len(all), 0)

queriedAccount, err := ts.accountRepo.FindById(uuid.UUID(account.ID))
ts.NoError(err)
ts.Equal(account.AccountName, queriedAccount.AccountName)
ts.Equal(account.ID, queriedAccount.ID)
}

func (ts *AccountRepositoryTestSuite) TearDownSuite() {
err := ts.sqliteConnect.CleanUp()
ts.NoError(err)
}

func TestSuiteRunnerAccountRepository(t *testing.T) {
ts := new(AccountRepositoryTestSuite)
suite.Run(t, ts)
}

0 comments on commit 4e9f855

Please sign in to comment.