Michał Szymański
13/12/2019
First release November 10, 2009.
The architecture does not depend on the existence of some library.
The business logic can be tested without external elements ie UI, databse.
Independent of UI. The UI can change easily, without changing the rest of the system.
Can change to other databse.
In fact your business rules simply don’t know anything at all about the outside world.
import "time"
type Article struct {
ID int64 `json:"id"`
Title string `json:"title"`
Edition string `json:"edition"`
Content string `json:"content"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
func (article *Article) FullTitle() string {
return article.Title + " " + article.Edition
}
package repository
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/bmiles-development/shopify-game-player/player/models"
)
type ArticleRepo struct{
DB *sql.DB
}
func (repo *ArticleRepo) FindByID(id int64) (*models.Article, error) {
result, err := repo.DB.Query("SELECT * FROM articles WHERE id = ?", id)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
defer result.Close()
var article models.Article
for result.Next() {
// for each row, scan the result into our article composite object
err = result.Scan(&article.ID, &article.Title, &article.Content)
if err != nil {
panic(err.Error()) // proper error handling instead of panic in your app
}
}
return &article, err
}
func (repo *ArticleRepo) Save(article *models.Article) error {
sqlQuery, err := repo.DB.Prepare("INSERT INTO articles(title, edition, content) VALUES(?, ?, ?)")
if err != nil {
panic(err.Error())
}
sqlQuery.Exec(article.Title, article.Edition, article.Content)
return err
}
package usecase
import (
"github.com/bmiles-development/shopify-game-player/player/models"
"github.com/bmiles-development/shopify-game-player/player/repository"
)
type UserUsecase struct {
userRepo repository.UserRepo
userShopifyRepo repository.UserShopify
}
func NewUserUseCase(userRepo repository.UserRepo, userShopifyRepo repository.UserShopify) *UserUsecase {
return &UserUsecase{
userRepo: userRepo,
userShopifyRepo: userShopifyRepo,
}
}
func (userUseCase *UserUsecase) SyncUsers(userCount int) []models.User {
return userUseCase.userShopifyRepo.Fetch(userCount)
}
func (userUseCase *UserUsecase) AddTagsToShopifyUsers(userID string, tags string) models.User {
return userUseCase.userShopifyRepo.AddTags(userID, tags)
}
package controller
import (
"net/http"
"github.com/manakuro/golang-clean-architecture/domain/model"
"github.com/manakuro/golang-clean-architecture/usecase/interactor"
)
type userController struct {
userInteractor interactor.UserInteractor
}
type UserController interface {
GetUsers(c Context) error
}
func NewUserController(us interactor.UserInteractor) UserController {
return &userController{us}
}
func (uc *userController) GetUsers(c Context) error {
var u []*model.User
u, err := uc.userInteractor.Get(u)
if err != nil {
return err
}
return c.JSON(http.StatusOK, u)
}
//Deliveries
var UserLambda *userDelivery.LambdaDelivery
//Usecases
var userUC *userUsecase.UserUsecase
func Init() error {
var err error
//Connections
loadEnvs()
dbConection := dbConnection()
graphqlConnection := playerConnection.GraphqlConnection{
Host: os.Getenv("SHOPIFY_HOST"),
Token: os.Getenv("SHOPIFY_TOKEN"),
}
//Repos
userRepo := playerRepository.NewMysqlUserRepository(dbConection)
shopifyUserRepo := playerRepository.NewPlayerGrapqlRepository(graphqlConnection)
//levelRepo := levelRepository.NewMysqlLevelRepository(dbConection)
//Usecases
userUC := userUsecase.NewUserUseCase(*userRepo, *shopifyUserRepo)
//Delivery
UserLambda = userDelivery.NewLambdaDelivery(*userUC)
return err
}
func loadEnvs() {
path := calcAppPath()
err := godotenv.Load(path + "/.env")
if err != nil {
//log.Fatal("Error loading .env file")
}
}
func calcAppPath() string {
appPath := os.Getenv("APP_PATH")
if appPath != "" {
return appPath
}
cmd := exec.Command("/bin/pwd")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
panic(err)
}
parts := strings.Split(strings.TrimSpace(out.String()), string(os.PathSeparator))
return strings.Join(parts, string(os.PathSeparator))
}
func dbConnection() *sql.DB {
dbHost := os.Getenv("MYSQL_HOST")
dbPort := os.Getenv("MYSQL_PORT")
dbUser := os.Getenv("MYSQL_USER")
dbPassword := os.Getenv("MYSQL_PASSWORD")
dbName := os.Getenv("MYSQL_DATABASE")
dbCredentials := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPassword, dbHost, dbPort, dbName)
return playerConnection.Connect(dbCredentials)
}