استفاده از SQLBoiler و Wire در معماری لایه ای با Go

Summarize this content to 400 words in Persian Lang
0. برای شروع
من این مقاله را در حالی نوشتم که معماری لایهای را پیادهسازی کردم و در عین حال نحوه استفاده را یاد گرفتم Wire.
قبل از این کار، فایل Docker Compose با کانتینر Go backend، MySQL و Adminer تنظیم شده است.
علاوه بر این، تنظیمات SQLBoiler و Wire نیز تکمیل شده است.
این ساختار دایرکتوری است.
| .gitignore
| docker-compose.yml
| README.md
|
+—backend
| | .air.toml
| | Dockerfile
| | go.mod
| | go.sum
| | main.go
| | sqlboiler.toml
| |
| +—domain
| | +—entity
| | | book.go
| | |
| | \—repository
| | book.go
| |
| +—infrastructure
| | \—repositoryImpl
| | book.go
| |
| +—interface
| | \—handler
| | book.go
| | rooter.go
| |
| +—mysql
| | db.go
| |
| +—sqlboiler (auto generated)
| | boil_queries.go
| | boil_table_names.go
| | boil_types.go
| | books.go
| | mysql_upsert.go
| |
| |
| +—usecase
| | book_repository.go
| |
| \—wire
| wire.go
| wire_gen.go
|
\—initdb
init.sql
1. initdb/init.sql
با پیکربندی docker-entrypoint-initdb.d در فایل docker-compose.yaml، جداول بهطور خودکار ایجاد میشوند.
CREATE TABLE books (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
2. فایل های sqlboiler ایجاد کنید
اجرا کنید sqlboiler mysql
فایل ها در ایجاد می شوند /sqlboiler
3. NewDB را پیاده سازی کنید
من روش اتصال پایگاه داده را برای فراخوانی در لایه زیرساخت پیاده سازی می کنم.
/mysql/db.go
package mysql
import (
“database/sql”
“fmt”
_ “github.com/go-sql-driver/mysql”
)
type DBConfig struct {
User string
Password string
Host string
Port int
DBName string
}
func NewDB() (*sql.DB, error) {
cfg := DBConfig{
User: “sample”,
Password: “sample”,
Host: “sample”,
Port: 3306,
DBName: “sample”,
}
dsn := fmt.Sprintf(“%s:%s@tcp(%s:%d)/%s”, cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName)
db, err := sql.Open(“mysql”, dsn)
if err != nil {
return nil, err
}
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}
4. دامنه، Usecase، زیرساخت و رابط را پیاده سازی کنید
/domain/entity/book.go
package entity
type Book struct {
Id int
Name string
}
/domain/repository/book.go
package repository
import “main/domain/entity”
type BookRepository interface {
Save(book *entity.Book) error
}
/usecase/book.go
package usecase
import (
“main/domain/entity”
“main/domain/repository”
)
type BookUsecase interface {
Save(book *entity.Book) error
}
type bookUsecaseImpl struct {
bookRepository repository.BookRepository
}
func NewBookUsecaseImpl(br repository.BookRepository) BookUsecase {
return &bookUsecaseImpl{bookRepository: br}
}
func (bu *bookUsecaseImpl) Save(book *entity.Book) error {
if err := bu.bookRepository.Save(book); err != nil {
return err
}
return nil
}
/infrastructure/repositoryImpl/book.go
package repositoryImpl
import (
“context”
“database/sql”
“main/domain/entity”
“main/domain/repository”
“main/sqlboiler”
“github.com/volatiletech/sqlboiler/v4/boil”
)
type bookRepositoryImpl struct {
db *sql.DB
}
func NewBookRepositoryImpl(db *sql.DB) repository.BookRepository {
return &bookRepositoryImpl{db: db}
}
func (br *bookRepositoryImpl) Save(book *entity.Book) error {
bookModel := &sqlboiler.Book{
ID: book.Id,
Name: book.Name,
}
err := bookModel.Insert(context.Background(), br.db, boil.Infer())
if err != nil {
return err
}
return nil
}
/interface/handler/book.go
برای APIهایی که در زیر /book دسته بندی می شوند، در این کنترل کننده پیاده سازی خواهند شد.
package handler
import (
“main/domain/entity”
“main/usecase”
“net/http”
“github.com/labstack/echo/v4”
)
type BookHandler struct {
bookUsecase usecase.BookUsecase
}
func (bh *BookHandler) RegisterRoutes(e *echo.Echo) {
e.POST(“/book”, bh.SaveBook)
}
func NewBookHandler(bu usecase.BookUsecase) *BookHandler {
return &BookHandler{bookUsecase: bu}
}
func (bh *BookHandler) SaveBook(c echo.Context) error {
book := new(entity.Book)
if err := c.Bind(book); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{“error”: “invalid request”})
}
if err := bh.bookUsecase.Save(book); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{“error”: err.Error()})
}
return c.JSON(http.StatusOK, book)
}
/interface/handler/router.go
من router.go را با انتظار ایجاد چندین API ایجاد می کنم.
package handler
import (
“github.com/labstack/echo/v4”
)
func RegisterRoutes(e *echo.Echo, bookHandler *BookHandler) {
bookHandler.RegisterRoutes(e)
}
func NewEchoInstance(bookHandler *BookHandler) *echo.Echo {
e := echo.New()
RegisterRoutes(e, bookHandler)
return e
}
5. wire.go را پیاده سازی کنید
من wire.go را برای مدیریت فایل های پیاده سازی شده با تزریق وابستگی پیاده سازی می کنم. از آنجایی که router.go برمی گردد e، این قسمت نیز در wire.go قرار خواهد گرفت.
به نظر می رسد //go:build wireinject دستورالعمل تضمین می کند که فایل فقط در مرحله تولید کد با Wire گنجانده شده است و از ساخت نهایی حذف می شود.
//go:build wireinject
package wire
import (
“main/infrastructure/repositoryImpl”
“main/interface/handler”
“main/mysql”
“main/usecase”
“github.com/google/wire”
“github.com/labstack/echo/v4”
)
func InitializeEcho() (*echo.Echo, error) {
wire.Build(
mysql.NewDB,
repositoryImpl.NewBookRepositoryImpl,
usecase.NewBookUsecaseImpl,
handler.NewBookHandler,
handler.NewEchoInstance,
)
return nil, nil
}
6. wire_gen.go را ایجاد کنید
wire_gen.go یک فایل به صورت خودکار بر اساس وابستگی های مشخص شده در wire.go است.
اجرا کنید wire در /wire.
wire.go به این صورت تولید خواهد شد.
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
“github.com/labstack/echo/v4”
“main/infrastructure/repositoryImpl”
“main/interface/handler”
“main/mysql”
“main/usecase”
)
// Injectors from wire.go:
func InitializeEcho() (*echo.Echo, error) {
db, err := mysql.NewDB()
if err != nil {
return nil, err
}
bookRepository := repositoryImpl.NewBookRepositoryImpl(db)
bookUsecase := usecase.NewBookUsecaseImpl(bookRepository)
bookHandler := handler.NewBookHandler(bookUsecase)
echoEcho := handler.NewEchoInstance(bookHandler)
return echoEcho, nil
}
7. تایید
پس از ارسال درخواست از طریق API، توانستم تأیید کنم که داده ها با موفقیت در پایگاه داده ذخیره شده است.
8. در نتیجه
ممنون که خواندید. در این پست، من یک API ساده با استفاده از Wire و SQLBoiler در یک معماری لایه ای ایجاد کردم. من همچنین یاد گرفتم که چگونه Wire می تواند مدیریت وابستگی ها را ساده تر کند، حتی اگر پیچیده تر شوند. اگر متوجه اشتباهی شدید، لطفاً به من اطلاع دهید.
0. برای شروع
من این مقاله را در حالی نوشتم که معماری لایهای را پیادهسازی کردم و در عین حال نحوه استفاده را یاد گرفتم Wire
.
قبل از این کار، فایل Docker Compose با کانتینر Go backend، MySQL و Adminer تنظیم شده است.
علاوه بر این، تنظیمات SQLBoiler و Wire نیز تکمیل شده است.
این ساختار دایرکتوری است.
| .gitignore
| docker-compose.yml
| README.md
|
+---backend
| | .air.toml
| | Dockerfile
| | go.mod
| | go.sum
| | main.go
| | sqlboiler.toml
| |
| +---domain
| | +---entity
| | | book.go
| | |
| | \---repository
| | book.go
| |
| +---infrastructure
| | \---repositoryImpl
| | book.go
| |
| +---interface
| | \---handler
| | book.go
| | rooter.go
| |
| +---mysql
| | db.go
| |
| +---sqlboiler (auto generated)
| | boil_queries.go
| | boil_table_names.go
| | boil_types.go
| | books.go
| | mysql_upsert.go
| |
| |
| +---usecase
| | book_repository.go
| |
| \---wire
| wire.go
| wire_gen.go
|
\---initdb
init.sql
1. initdb/init.sql
با پیکربندی docker-entrypoint-initdb.d در فایل docker-compose.yaml، جداول بهطور خودکار ایجاد میشوند.
CREATE TABLE books (
id INT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
2. فایل های sqlboiler ایجاد کنید
اجرا کنید sqlboiler mysql
فایل ها در ایجاد می شوند /sqlboiler
3. NewDB را پیاده سازی کنید
من روش اتصال پایگاه داده را برای فراخوانی در لایه زیرساخت پیاده سازی می کنم.
/mysql/db.go
package mysql
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
type DBConfig struct {
User string
Password string
Host string
Port int
DBName string
}
func NewDB() (*sql.DB, error) {
cfg := DBConfig{
User: "sample",
Password: "sample",
Host: "sample",
Port: 3306,
DBName: "sample",
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName)
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}
4. دامنه، Usecase، زیرساخت و رابط را پیاده سازی کنید
/domain/entity/book.go
package entity
type Book struct {
Id int
Name string
}
/domain/repository/book.go
package repository
import "main/domain/entity"
type BookRepository interface {
Save(book *entity.Book) error
}
/usecase/book.go
package usecase
import (
"main/domain/entity"
"main/domain/repository"
)
type BookUsecase interface {
Save(book *entity.Book) error
}
type bookUsecaseImpl struct {
bookRepository repository.BookRepository
}
func NewBookUsecaseImpl(br repository.BookRepository) BookUsecase {
return &bookUsecaseImpl{bookRepository: br}
}
func (bu *bookUsecaseImpl) Save(book *entity.Book) error {
if err := bu.bookRepository.Save(book); err != nil {
return err
}
return nil
}
/infrastructure/repositoryImpl/book.go
package repositoryImpl
import (
"context"
"database/sql"
"main/domain/entity"
"main/domain/repository"
"main/sqlboiler"
"github.com/volatiletech/sqlboiler/v4/boil"
)
type bookRepositoryImpl struct {
db *sql.DB
}
func NewBookRepositoryImpl(db *sql.DB) repository.BookRepository {
return &bookRepositoryImpl{db: db}
}
func (br *bookRepositoryImpl) Save(book *entity.Book) error {
bookModel := &sqlboiler.Book{
ID: book.Id,
Name: book.Name,
}
err := bookModel.Insert(context.Background(), br.db, boil.Infer())
if err != nil {
return err
}
return nil
}
/interface/handler/book.go
برای APIهایی که در زیر /book دسته بندی می شوند، در این کنترل کننده پیاده سازی خواهند شد.
package handler
import (
"main/domain/entity"
"main/usecase"
"net/http"
"github.com/labstack/echo/v4"
)
type BookHandler struct {
bookUsecase usecase.BookUsecase
}
func (bh *BookHandler) RegisterRoutes(e *echo.Echo) {
e.POST("/book", bh.SaveBook)
}
func NewBookHandler(bu usecase.BookUsecase) *BookHandler {
return &BookHandler{bookUsecase: bu}
}
func (bh *BookHandler) SaveBook(c echo.Context) error {
book := new(entity.Book)
if err := c.Bind(book); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
}
if err := bh.bookUsecase.Save(book); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
}
return c.JSON(http.StatusOK, book)
}
/interface/handler/router.go
من router.go را با انتظار ایجاد چندین API ایجاد می کنم.
package handler
import (
"github.com/labstack/echo/v4"
)
func RegisterRoutes(e *echo.Echo, bookHandler *BookHandler) {
bookHandler.RegisterRoutes(e)
}
func NewEchoInstance(bookHandler *BookHandler) *echo.Echo {
e := echo.New()
RegisterRoutes(e, bookHandler)
return e
}
5. wire.go را پیاده سازی کنید
من wire.go را برای مدیریت فایل های پیاده سازی شده با تزریق وابستگی پیاده سازی می کنم. از آنجایی که router.go برمی گردد e
، این قسمت نیز در wire.go قرار خواهد گرفت.
به نظر می رسد //go:build wireinject
دستورالعمل تضمین می کند که فایل فقط در مرحله تولید کد با Wire گنجانده شده است و از ساخت نهایی حذف می شود.
//go:build wireinject
package wire
import (
"main/infrastructure/repositoryImpl"
"main/interface/handler"
"main/mysql"
"main/usecase"
"github.com/google/wire"
"github.com/labstack/echo/v4"
)
func InitializeEcho() (*echo.Echo, error) {
wire.Build(
mysql.NewDB,
repositoryImpl.NewBookRepositoryImpl,
usecase.NewBookUsecaseImpl,
handler.NewBookHandler,
handler.NewEchoInstance,
)
return nil, nil
}
6. wire_gen.go را ایجاد کنید
wire_gen.go یک فایل به صورت خودکار بر اساس وابستگی های مشخص شده در wire.go است.
اجرا کنید wire
در /wire
.
wire.go
به این صورت تولید خواهد شد.
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
"github.com/labstack/echo/v4"
"main/infrastructure/repositoryImpl"
"main/interface/handler"
"main/mysql"
"main/usecase"
)
// Injectors from wire.go:
func InitializeEcho() (*echo.Echo, error) {
db, err := mysql.NewDB()
if err != nil {
return nil, err
}
bookRepository := repositoryImpl.NewBookRepositoryImpl(db)
bookUsecase := usecase.NewBookUsecaseImpl(bookRepository)
bookHandler := handler.NewBookHandler(bookUsecase)
echoEcho := handler.NewEchoInstance(bookHandler)
return echoEcho, nil
}
7. تایید
پس از ارسال درخواست از طریق API، توانستم تأیید کنم که داده ها با موفقیت در پایگاه داده ذخیره شده است.
8. در نتیجه
ممنون که خواندید. در این پست، من یک API ساده با استفاده از Wire و SQLBoiler در یک معماری لایه ای ایجاد کردم. من همچنین یاد گرفتم که چگونه Wire می تواند مدیریت وابستگی ها را ساده تر کند، حتی اگر پیچیده تر شوند. اگر متوجه اشتباهی شدید، لطفاً به من اطلاع دهید.