Посмотрите, как мой проект Go-zero делает его еще проще

Когда использовать RESTful API

Для большинства стартапов мы должны больше сосредоточиться на предоставлении продуктов на ранней стадии бизнеса. Преимущества монолитных сервисов заключаются в простой архитектуре, легком развертывании и более высокой производительности разработки, что может помочь нам быстро выполнить требования к продукту. Хотя мы используем монолитные сервисы для быстрой доставки продуктов, нам также необходимо оставлять возможность для расширения бизнеса, поэтому мы обычно четко разделяем различные бизнес-модули на монолитные сервисы.

Монолитная сервисная архитектура торгового центра

Мы берем торговый центр в качестве примера для построения монолитного сервиса. Служба торгового центра, как правило, относительно сложна и состоит из нескольких модулей, наиболее важные модули включают модули учетной записи, продукта и заказа и т. д. Каждый модуль будет иметь свою собственную независимую бизнес-логику, и каждый модуль также будет зависеть от некоторых других. Например, модуль заказа и модуль продукта будут зависеть от модуля учетной записи. В монолитном приложении этот вид зависимости обычно реализуется вызовами методов между модулями. Монолитные службы обычно совместно используют ресурсы хранения, такие как MySQL и Redis.

Общая архитектура монолитных сервисов относительно проста, что также является преимуществом монолитных сервисов. Запросы клиентов анализируются через DNS и перенаправляются в серверные службы торгового центра через Nginx. Сервисы торгового центра развернуты на облачных хостах. Чтобы добиться большей пропускной способности и высокой доступности, служба обычно развертывается с несколькими копиями. Эта простая архитектура может обеспечить высокую пропускную способность, если она хорошо оптимизирована.

Например, запрос на интерфейс сведений о заказе /order/detail направляется в модуль заказа, который использует модуль учетной записи и модуль продукта для составления полных сведений о заказе обратно пользователю, а несколько модулей в одной службе обычно совместно используют базу данных и кеш.

Монолитный сервис

В следующем разделе описывается, как быстро реализовать монолитный сервис торгового центра на основе go-zero. Разработчики, которые использовали go-zero, знают, что мы предоставляем файл формата API для описания Restful API, а затем мы можем сгенерировать соответствующий код с помощью goctl с помощью одной команды, нам просто нужно заполнить соответствующую бизнес-логику в файлах logic. Служба торгового центра содержит несколько модулей, и чтобы сделать модули независимыми друг от друга, разные модули определяются отдельными API, но все API определяются для одного и того же service (mall-api).

Создайте user.api, order.api, product.api и mall.api в каталоге api, где mall.api – это объединенный файл api. Другие файлы api импортируются через директивы import.

api
|-- mall.api
|-- order.api
|-- product.api
|-- user.api

Определение API торгового центра

mall.api определяется следующим образом, где syntax = "v1" означает, что это синтаксис v1 для zero-api.

syntax = "v1"
import "user.api"
import "order.api"
import "product.api"

Определение API модуля учетной записи

  • Просмотр сведений о пользователе
  • Получить все заказы для пользователя
syntax = "v1"
type (
    UserRequest {
        ID int64 `path:"id"`
    }
    UserReply {
        ID      int64   `json:"id"`
        Name    string  `json:"name"`
        Balance float64 `json:"balance"`
    }
    UserOrdersRequest {
        ID int64 `path:"id"`
    }
    UserOrdersReply {
        ID       string `json:"id"`
        State    uint32 `json:"state"`
        CreateAt string `json:"create_at"`
    }
)
service mall-api {
    @handler UserHandler
    get /user/:id (UserRequest) returns (UserReply)
    @handler UserOrdersHandler
    get /user/:id/orders (UserOrdersRequest) returns (UserOrdersReply)
}

Определение API модуля заказа

  • Получить детали заказа
  • Генерировать заказы
syntax = "v1"
type (
    OrderRequest {
        ID string `path:"id"`
    }
    OrderReply {
        ID       string `json:"id"`
        State    uint32 `json:"state"`
        CreateAt string `json:"create_at"`
    }
    OrderCreateRequest {
        ProductID int64 `json:"product_id"`
    }
    OrderCreateReply {
        Code int `json:"code"`
    }
)
service mall-api {
    @handler OrderHandler
    get /order/:id (OrderRequest) returns (OrderReply)
    @handler OrderCreateHandler
    post /order/create (OrderCreateRequest) returns (OrderCreateReply)
}

Определение API модуля продукта

  • Посмотреть сведения о продукте
syntax = "v1"
type ProductRequest {
    ID int64 `path:"id"`
}
type ProductReply {
    ID    int64   `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price"`
    Count int64   `json:"count"`
}
service mall-api {
    @handler ProductHandler
    get /product/:id (ProductRequest) returns (ProductReply)
}

Создание монолитного сервиса

С уже определенным API создание службы с API становится очень простым, мы используем goctl для создания монолитного кода службы.

$ goctl api go -api api/mall.api -dir .

Сгенерированный код структурирован следующим образом.

.
├── api
│ ├── mall.api
│ ├── order.api
│ ├── product.api
│ └── user.api
├── etc
│ └── mall-api.yaml
├─ internal
│ ├── config
│ │ └── config.go
│ ├── handler
│ │ ├── ordercreatehandler.go
│ │ ├── orderhandler.go
│ │ ├── producthandler.go
│ │ ├── routes.go
│ │ ├── userhandler.go
│ │ └─ userordershandler.go
│ ├─ logic
│ │ ├─ ordercreatelogic.go
│ │ ├── orderlogic.go
│ │ ├── productlogic.go
│ │ ├── userlogic.go
│ │ └── userorderslogic.go
│ ├── svc
│ │ └── servicecontext.go
│ └── types
│ └── types.go
└── mall.go

Давайте объясним сгенерированные файлы.

  • api: содержит файл описания API
  • etc: используется для определения конфигурации проекта, все элементы конфигурации могут быть записаны в mall-api.yaml
  • internal/config: определение конфигурации службы
  • internal/handler: реализация handler, соответствующая маршрутам, определенным в файле API
  • internal/logic: используется для размещения бизнес-логики, соответствующей каждому маршруту, причина различия между handler и logic заключается в том, чтобы сделать часть бизнес-обработки как можно менее зависимой, отделить HTTP requests от кода логической обработки и облегчить последующее разделение в RPC service
  • internal/svc: используется для определения зависимостей обработки бизнес-логики, мы можем создать зависимые ресурсы в функции main и передать их handler и logic через ServiceContext
  • internal/types: определяет API структуры данных запроса и ответа.
  • mall.go: файл, в котором находится функция main, с тем же именем, что и service в определении API, за исключением суффикса -api.

Сгенерированную службу можно запустить без каких-либо изменений: `

$ go run mall.go
Starting server at 0.0.0.0:8888...

Реализация бизнес-логики

Далее реализуем бизнес-логику. Логика будет проста для демонстрационных целей, а не для реальной бизнес-логики.

Для начала реализуем логику получения всех заказов для пользователей. Поскольку в пользовательском модуле нет информации о заказах, нам нужно полагаться на модуль заказов для запроса заказов пользователей, поэтому мы добавляем зависимость от OrderLogic в UserOrdersLogic.

type UserOrdersLogic struct {
    logx.Logger
    ctx        context.Context
    svcCtx     *svc.ServiceContext
    orderLogic *OrderLogic
}
func NewUserOrdersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserOrdersLogic {
    return &UserOrdersLogic{
        Logger:     logx.WithContext(ctx),
        ctx:        ctx,
        svcCtx:     svcCtx,
        orderLogic: NewOrderLogic(ctx, svcCtx),
    }
}

Реализуйте метод в OrderLogic для запроса всех заказов на основе user id

func (l *OrderLogic) ordersByUser(uid int64) ([]*types.OrderReply, error) {
    if uid == 123 {
        // It should actually be queried from database or cache
        return []*types.OrderReply{
            {
                ID:       "236802838635",
                State:    1,
                CreateAt: "2022-5-12 22:59:59",
            },
            {
                ID:       "236802838636",
                State:    1,
                CreateAt: "2022-5-10 20:59:59",
            },
        }, nil
    }
    return nil, nil
}

Вызовите метод ordersByUser в методе UserOrders UserOrdersLogic.

func (l *UserOrdersLogic) UserOrders(req *types.UserOrdersRequest) (*types.UserOrdersReply, error) {
    orders, err := l.orderLogic.ordersByUser(req.ID)
    if err ! = nil {
        return nil, err
    }
    return &types.UserOrdersReply{
        Orders: orders,
    }, nil
}

В этот момент мы перезапускаем службу mall-api и запрашиваем все заказы пользователя в браузере.

https://localhost:8888/user/123/orders

Результат возврата выглядит следующим образом, как мы и ожидали

{
    "orders": [
        {
            "id": "236802838635",
            "state": 1,
            "create_at": "2022-5-12 22:59:59"
        },
        {
            "id": "236802838636",
            "state": 1,
            "create_at": "2022-5-10 20:59:59"
        }
    ]
}

Далее реализуем логику создания заказа. Чтобы создать заказ, нам сначала нужно посмотреть, достаточно ли товара на складе, поэтому нам нужно полагаться на модуль товара в модуле заказа.

type OrderCreateLogic struct {
    logx.Logger
    ctx          context.Context
    svcCtx       *svc.ServiceContext
    productLogic *ProductLogic
    productLogic *ProductLogic
}
func NewOrderCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *OrderCreateLogic {
    return &OrderCreateLogic{
        Logger:       logx.WithContext(ctx),
        ctx:          ctx,
        svcCtx:       svcCtx,
        productLogic: NewProductLogic(ctx, svcCtx),
    }
}

Логика создания заказа следующая.

const (
    success = 0
    failure = -1
)
func (l *OrderCreateLogic) OrderCreate(req *types.OrderCreateRequest) (*types.OrderCreateReply, error) {
    product, err := l.productLogic.productByID(req.ProductID)
    if err ! = nil {
        return nil, err
    }
    if product.Count > 0 {
        return &types.OrderCreateReply{Code: success}, nil
    }
    return &types.OrderCreateReply{Code: failure}, nil
}

Логика модуля зависимого продукта следующая.

func (l *ProductLogic) Product(req *types.ProductRequest) (*types.ProductReply, error) {
    return l.productByID(req.ID)
}
func (l *ProductLogic) productByID(id int64) (*types.ProductReply, error) {
    return &types.ProductReply{
        ID: id,
        Name: "apple watch 3",
        Price: 3333.33,
        Count: 99,
    }, nil
}

Вышеизложенное показывает, что использование go-zero для разработки монолитного сервиса очень просто, что помогает нам быстро развиваться. А еще мы разделили модули, что тоже дает возможность позже перейти на микросервисы.

Краткое содержание

Приведенный выше пример показывает, что очень просто использовать go-zero для разработки монолитных сервисов. Вам нужно только определить файл api, после чего инструмент goctl сможет автоматически сгенерировать код проекта. Нам нужно только заполнить код бизнес-логики в пакете логики. В этой статье мы просто продемонстрировали, как быстро разрабатывать монолитные сервисы на основе go-zero, не задействуя базы данных. Фактически, goctl также может генерировать код CRUD и cache одной командой.

Для различных бизнес-сценариев также можно настроить шаблоны. А кастомизированные шаблоны можно использовать внутри команды через удаленные git репозитории, что может быть очень эффективным для совместной работы.



Want to Connect?
Welcome to use go-zero and star to support us!