Разработка RESTful API с помощью Go


Когда использовать 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 и звезда, чтобы поддержать нас!

Оцените статью
Procodings.ru
Добавить комментарий