Сервис корзины, gRPC-метод AddLineAndCheckout: добавить позицию в корзину
покупателя и сразу оформить заказ. Каркас метода, сущности и зависимости уже
есть (в support.go), осталось дописать логику.
type Cart struct {
ID uint64
CustomerID uint64
Lines []CartLine
Status string // StatusOpen = "open", StatusPlaced = "placed"
GrandTotal uint64
}
type CartLine struct {
CartID, SKU, Qty, UnitPrice uint64
}
type CartRepository interface {
Load(ctx context.Context, customerID uint64) (*Cart, error)
Save(ctx context.Context, c *Cart) error
}
type OrderNotifier interface { // продюсер в брокер
SendMessage(ctx context.Context, cart Cart) error
}
type AddLineRequest struct {
CustomerID, SKU, UnitPrice, Qty uint64
}
var (
ErrAlreadyPlaced = errors.New("cart is already placed")
ErrInvalidQty = errors.New("qty must be positive")
)
func (svc *CartServer) AddLineAndCheckout(ctx context.Context, req *AddLineRequest) (*Ack, error)
Требования
req.Qty == 0 → ErrInvalidQty.
- В оформленную корзину (
Status == "placed") вносить изменения нельзя:
ошибка ErrAlreadyPlaced, ни Save, ни уведомления.
- Позиции уникальны по
SKU. Если SKU уже в корзине, количество
складывается, а цена берётся из запроса (актуальная). Новая позиция получает
CartID = cart.ID.
- После изменения состава
GrandTotal = sum(Qty * UnitPrice) по всем позициям.
- Статус меняется на
"placed", корзина сохраняется через repo.Save.
- После успешного сохранения подписчики уведомляются через
notifier.SendMessage уже оформленной корзиной. Если Save упал, уведомлять
нельзя.
- Ошибки зависимостей возвращаются обёрнутыми:
errors.Is находит исходную.
Пример
// в репозитории: Cart{ID: 7, CustomerID: 1, Status: "open",
// Lines: [{SKU: 100, Qty: 1, UnitPrice: 500}], GrandTotal: 500}
svc.AddLineAndCheckout(ctx, &AddLineRequest{CustomerID: 1, SKU: 200, Qty: 2, UnitPrice: 300})
// сохранено и отправлено в брокер: Status "placed", GrandTotal 1100,
// Lines: [{7 100 1 500} {7 200 2 300}]
Устно: Save прошёл, а SendMessage упал. Клиент ретраит запрос и получает
ErrAlreadyPlaced. Что сломалось и как это чинят (transactional outbox)?
Что будет, если два запроса одного покупателя придут одновременно?