برنامه نویسی

استفاده از 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، توانستم تأیید کنم که داده ها با موفقیت در پایگاه داده ذخیره شده است.

درخواست API

دسی بی

8. در نتیجه

ممنون که خواندید. در این پست، من یک API ساده با استفاده از Wire و SQLBoiler در یک معماری لایه ای ایجاد کردم. من همچنین یاد گرفتم که چگونه Wire می تواند مدیریت وابستگی ها را ساده تر کند، حتی اگر پیچیده تر شوند. اگر متوجه اشتباهی شدید، لطفاً به من اطلاع دهید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا