Когда использовать RESTful API
Для большинства стартапов на ранней стадии бизнеса мы должны больше сосредоточиться на выпуске продуктов. Монолитные сервисы имеют преимущества простой архитектуры, легкого развертывания и более высокой производительности разработки, что может помочь нам быстро достичь требований к продукту. Хотя мы используем монолитные сервисы для быстрой доставки продуктов, нам также необходимо оставить возможность для инкрементации бизнеса, поэтому мы обычно четко разделяем различные бизнес-модули в монолитных сервисах.
Архитектура монолитного сервиса торгового центра
В качестве примера для построения монолитного сервиса мы возьмем торговый центр. Сервис торгового центра обычно относительно сложен и состоит из множества модулей, более важные модули включают в себя учетную запись, модули продуктов, заказов и т.д. Каждый модуль будет иметь свою собственную независимую бизнес-логику, и каждый модуль также будет зависеть от некоторых других. Например, модуль заказа и модуль продукта будут зависеть от модуля счета. В монолитном приложении такая зависимость обычно достигается вызовами методов между модулями. Монолитные сервисы обычно совместно используют ресурсы хранения данных, такие как MySQL
и Redis
.
Общая архитектура монолитных сервисов относительно проста, что также является преимуществом монолитных сервисов. Запросы клиентов обрабатываются через DNS
и направляются к внутренним сервисам торгового центра через Nginx
. Сервисы торгового центра развернуты на облачных хостах. Для достижения большей пропускной способности и высокой доступности сервис обычно развертывается в нескольких копиях. Эта простая архитектура может обеспечить высокую пропускную способность, если она хорошо оптимизирована.
Например, запрос на детали заказа через интерфейс /order/detail
направляется к модулю заказа, который опирается на модуль счета и модуль продукта для составления полной информации о заказе обратно пользователю, а несколько модулей в одном сервисе обычно совместно используют базу данных и кэш.
Монолитный сервис
В следующем разделе описывается, как быстро реализовать монолитный сервис, основанный на go-zero
. Разработчики, которые использовали go-zero
, знают, что мы предоставляем файл формата API
для описания Restful API
, а затем мы можем генерировать соответствующий код с помощью goctl
одной командой, нам просто нужно заполнить соответствующую бизнес-логику в файлах logic
. Сервис mall содержит несколько модулей, и чтобы сделать модули независимыми друг от друга, разные модули определяются отдельными API
, но все API
определены для одного сервиса (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 модуля учетной записи
- Просмотр сведений о пользователе
- Получить все заказы для пользователя
user.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 модуля заказа
- Получить информацию о заказе
- Генерировать заказы
order.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 модуля продукта
- Просмотр деталей продукта
product.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
Поясним сгенерированные файлы.
Сгенерированный сервис может быть запущен без каких-либо изменений:
$ 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
и запрашиваем все заказы пользователя в браузере.
http://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
, что может быть очень эффективно для совместной работы команды.
Адрес проекта
https://github.com/zeromicro/go-zero
Добро пожаловать на использование go-zero
и звезда, чтобы поддержать нас!