Skip to content

Commit

Permalink
feat: ✨ integrating with goose and atlas migration tools (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdihadeli authored Sep 20, 2023
1 parent cffda05 commit e55cc63
Show file tree
Hide file tree
Showing 78 changed files with 1,498 additions and 556 deletions.
8 changes: 0 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ out/
.idea/
local/data/
portainer-data/
.env
.husky/pre-commit

# Logs
Expand Down Expand Up @@ -138,13 +137,6 @@ web_modules/
# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
Expand Down
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,28 @@ lint:
@./scripts/lint.sh order_service
@./scripts/lint.sh pkg

# https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/database/postgres/TUTORIAL.md
# https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/GETTING_STARTED.md
# https://github.com/golang-migrate/migrate/blob/856ea12df9d230b0145e23d951b7dbd6b86621cb/MIGRATIONS.md
# https://github.com/golang-migrate/migrate/tree/856ea12df9d230b0145e23d951b7dbd6b86621cb/cmd/migrate#usage
.PHONY: go-migrate
go-migrate:
@./scripts/go-migrate.sh -p ./internal/services/catalog_write_service/db/migrations/go-migrate -c create -n create_product_table
@./scripts/go-migrate.sh -p ./internal/services/catalog_write_service/db/migrations/go-migrate -c up -o postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable
@./scripts/go-migrate.sh -p ./internal/services/catalog_write_service/db/migrations/go-migrate -c down -o postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable

# https://github.com/pressly/goose#usage
.PHONY: goose-migrate
goose-migrate:
@./scripts/goose-migrate.sh -p ./internal/services/catalog_write_service/db/migrations/goose-migrate -c create -n create_product_table
@./scripts/goose-migrate.sh -p ./internal/services/catalog_write_service/db/migrations/goose-migrate -c up -o "user=postgres password=postgres dbname=catalogs_service sslmode=disable"
@./scripts/goose-migrate.sh -p ./internal/services/catalog_write_service/db/migrations/goose-migrate -c down -o "user=postgres password=postgres dbname=catalogs_service sslmode=disable"

# https://atlasgo.io/guides/orms/gorm
.PHONY: atlas
atlas:
@./scripts/atlas-migrate.sh -c gorm-sync -p "./internal/services/catalog_write_service"
@./scripts/atlas-migrate.sh -c apply -p "./internal/services/catalog_write_service" -o "postgres://postgres:postgres@localhost:5432/catalogs_service?sslmode=disable"

#.PHONY: c4
#c4:
Expand Down
8 changes: 5 additions & 3 deletions internal/pkg/config/config_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ func BindConfigKey[T any](configKey string, environments ...environemnt.Environm
}

// https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d
configPathFromEnv := viper.Get(constants.ConfigPath)
if configPathFromEnv != nil {
configPath = configPathFromEnv.(string)
// when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string
// load `config path` from environment variable or viper internal registry
configPathFromEnv := viper.GetString(constants.ConfigPath)
if configPathFromEnv != "" {
configPath = configPathFromEnv
} else {
// https://stackoverflow.com/questions/31873396/is-it-possible-to-get-the-current-root-of-package-structure-as-a-string-in-golan
// https://stackoverflow.com/questions/18537257/how-to-get-the-directory-of-the-currently-running-file
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/config/configfx.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var ModuleFunc = func(e environemnt.Environment) fx.Option {
return fx.Module(
"configfx",
fx.Provide(func() environemnt.Environment {
return e
return environemnt.ConfigAppEnv(e)
}),
)
}
75 changes: 73 additions & 2 deletions internal/pkg/config/environemnt/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package environemnt
import (
"log"
"os"
"path/filepath"
"strings"
"syscall"

"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants"

"emperror.dev/errors"
"github.com/joho/godotenv"
"github.com/spf13/viper"
)
Expand All @@ -32,11 +35,13 @@ func ConfigAppEnv(environments ...Environment) Environment {

// https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d
// load environment variables form .env files to system environment variables, it just find `.env` file in our current `executing working directory` in our app for example `catalogs_service`
err := godotenv.Load()
err := loadEnvFilesRecursive()
if err != nil {
log.Println(".env file cannot be found.")
log.Printf(".env file cannot be found, err: %v", err)
}

setRootWorkingDirectoryEnvironment()

manualEnv := os.Getenv(constants.AppEnv)

if manualEnv != "" {
Expand Down Expand Up @@ -64,3 +69,69 @@ func EnvString(key, fallback string) string {
}
return fallback
}

func loadEnvFilesRecursive() error {
// Start from the current working directory
dir, err := os.Getwd()
if err != nil {
return err
}

// Keep traversing up the directory hierarchy until you find an ".env" file
for {
envFilePath := filepath.Join(dir, ".env")
err := godotenv.Load(envFilePath)

if err == nil {
// .env file found and loaded
return nil
}

// Move up one directory level
parentDir := filepath.Dir(dir)
if parentDir == dir {
// Reached the root directory, stop searching
break
}

dir = parentDir
}

return errors.New(".env file not found in the project hierarchy")
}

func setRootWorkingDirectoryEnvironment() {
// https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d
// when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string
// viper will get it from `os env` or a .env file
pn := viper.GetString(constants.PROJECT_NAME_ENV)
if pn == "" {
log.Fatalf(
"can't find environment variable `%s` in the system environment variables or a .env file",
constants.PROJECT_NAME_ENV,
)
}

// set root working directory of our app in the viper
// https://stackoverflow.com/a/47785436/581476
wd, _ := os.Getwd()

for !strings.HasSuffix(wd, pn) {
wd = filepath.Dir(wd)
}

absoluteRootWorkingDirectory, _ := filepath.Abs(wd)

// when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string
viper.Set(constants.AppRootPath, absoluteRootWorkingDirectory)

configPath := viper.GetString(constants.ConfigPath)

// check for existing `CONFIG_PATH` variable in system environment variables - we can set it directly in .env file or system environments
if configPath == "" {
configPath := filepath.Join(absoluteRootWorkingDirectory, "config")

// when we `Set` a viper with string value, we should get it from viper with `viper.GetString`, elsewhere we get empty string
viper.Set(constants.ConfigPath, configPath)
}
}
33 changes: 0 additions & 33 deletions internal/pkg/fxapp/application_builder.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
package fxapp

import (
"os"
"path/filepath"
"strings"

"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp/contracts"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger"
loggerConfig "github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/config"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/logrous"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/models"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger/zap"

"github.com/spf13/viper"
"go.uber.org/fx"
)

Expand All @@ -29,8 +23,6 @@ type applicationBuilder struct {
func NewApplicationBuilder(environments ...environemnt.Environment) contracts.ApplicationBuilder {
env := environemnt.ConfigAppEnv(environments...)

setConfigPath()

var logger logger.Logger
logoption, err := loggerConfig.ProvideLogConfig(env)
if err != nil || logoption == nil {
Expand All @@ -44,31 +36,6 @@ func NewApplicationBuilder(environments ...environemnt.Environment) contracts.Ap
return &applicationBuilder{logger: logger, environment: env}
}

func setConfigPath() {
// https://stackoverflow.com/a/47785436/581476
wd, _ := os.Getwd()

// https://articles.wesionary.team/environment-variable-configuration-in-your-golang-project-using-viper-4e8289ef664d
// get from `os env` or viper `internal registry`
pn := viper.Get(constants.PROJECT_NAME_ENV)
if pn == nil {
return
}
for !strings.HasSuffix(wd, pn.(string)) {
wd = filepath.Dir(wd)
}

// Get the absolute path of the executed project directory
absCurrentDir, _ := filepath.Abs(wd)

viper.Set(constants.AppRootPath, absCurrentDir)

// Get the path to the "config" folder within the project directory
configPath := filepath.Join(absCurrentDir, "config")

viper.Set(constants.ConfigPath, configPath)
}

func (a *applicationBuilder) ProvideModule(module fx.Option) {
a.options = append(a.options, module)
}
Expand Down
2 changes: 2 additions & 0 deletions internal/pkg/fxapp/contracts/application_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
)

type ApplicationBuilder interface {
// ProvideModule register modules directly instead and modules should not register with `provided` function
ProvideModule(module fx.Option)
// Provide register functions constructors as dependency resolver
Provide(constructors ...interface{})
Decorate(constructors ...interface{})
Build() Application
Expand Down
51 changes: 38 additions & 13 deletions internal/pkg/fxapp/test/test_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package test

import (
"context"
"os"

"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/config/environemnt"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/constants"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/fxapp/contracts"
"github.com/mehdihadeli/go-ecommerce-microservices/internal/pkg/logger"

"github.com/spf13/viper"
"go.uber.org/fx"
"go.uber.org/fx/fxtest"
)
Expand Down Expand Up @@ -61,16 +64,8 @@ func (a *testApplication) RegisterHook(function interface{}) {
}

func (a *testApplication) Run() {
// build phase of container will do in this stage, containing provides and invokes but app not started yet and will be started in the future with `fxApp.Register`
fxTestApp := CreateFxTestApp(
a.tb,
a.provides,
a.decorates,
a.invokes,
a.options,
a.logger,
a.env,
)
fxTestApp := a.createFxTest()

// running phase will do in this stage and all register event hooks like OnStart and OnStop
// instead of run for handling start and stop and create a ctx and cancel we can handle them manually with appconfigfx.start and appconfigfx.stop
// https://github.com/uber-go/fx/blob/v1.20.0/app.go#L573
Expand All @@ -79,10 +74,9 @@ func (a *testApplication) Run() {

func (a *testApplication) Start(ctx context.Context) error {
// build phase of container will do in this stage, containing provides and invokes but app not started yet and will be started in the future with `fxApp.Register`
fxApp := CreateFxTestApp(a.tb, a.provides, a.decorates, a.invokes, a.options, a.logger, a.env)
a.fxtestApp = fxApp
fxTestApp := a.createFxTest()

return fxApp.Start(ctx)
return fxTestApp.Start(ctx)
}

func (a *testApplication) Stop(ctx context.Context) error {
Expand All @@ -98,3 +92,34 @@ func (a *testApplication) Wait() <-chan fx.ShutdownSignal {
}
return a.fxtestApp.Wait()
}

func (a *testApplication) createFxTest() *fxtest.App {
a.fixTestEnvironmentWorkingDirectory()

// build phase of container will do in this stage, containing provides and invokes but app not started yet and will be started in the future with `fxApp.Register`
fxTestApp := CreateFxTestApp(
a.tb,
a.provides,
a.decorates,
a.invokes,
a.options,
a.logger,
a.env,
)
a.fxtestApp = fxTestApp

return fxTestApp
}

func (a *testApplication) fixTestEnvironmentWorkingDirectory() {
currentWD, _ := os.Getwd()
a.logger.Infof("Current test working directory is: %s", currentWD)

rootDir := viper.GetString(constants.AppRootPath)
if rootDir != "" {
_ = os.Chdir(rootDir)

newWD, _ := os.Getwd()
a.logger.Infof("New test working directory is: %s", newWD)
}
}
6 changes: 5 additions & 1 deletion internal/pkg/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/avast/retry-go v3.0.0+incompatible
github.com/caarlos0/env/v8 v8.0.0
github.com/docker/docker v24.0.5+incompatible
github.com/docker/go-connections v0.4.0
github.com/doug-martin/goqu/v9 v9.18.0
github.com/elastic/go-elasticsearch/v8 v8.9.0
Expand All @@ -33,11 +34,13 @@ require (
github.com/lib/pq v1.10.9
github.com/mcuadros/go-defaults v1.2.0
github.com/mehdihadeli/go-mediatr v1.1.10
github.com/michaelklishin/rabbit-hole v1.5.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nolleh/caption_json_formatter v0.2.2
github.com/orlangure/gnomock v0.30.0
github.com/ory/dockertest/v3 v3.10.0
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pressly/goose/v3 v3.15.0
github.com/prometheus/client_golang v1.16.0
github.com/rabbitmq/amqp091-go v1.8.1
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5
Expand Down Expand Up @@ -90,7 +93,6 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/docker/cli v24.0.5+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.5+incompatible // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elastic/elastic-transport-go/v8 v8.3.0 // indirect
github.com/fatih/color v1.15.0 // indirect
Expand Down Expand Up @@ -137,6 +139,7 @@ require (
github.com/moby/term v0.5.0 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
github.com/opencontainers/runc v1.1.9 // indirect
Expand All @@ -157,6 +160,7 @@ require (
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/streadway/amqp v1.1.0 // indirect
github.com/stretchr/objx v0.5.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
Expand Down
Loading

0 comments on commit e55cc63

Please sign in to comment.