From 99a5b8ba49edc8cb8f2d4906e0bc617baa01c463 Mon Sep 17 00:00:00 2001 From: marcus-sa <8391194+marcus-sa@users.noreply.github.com> Date: Wed, 14 Aug 2024 01:05:22 +0200 Subject: [PATCH] improvements --- accounting-service-api/src/index.ts | 2 +- accounting-service-api/src/lib/entities.ts | 9 +- accounting-service-api/src/lib/service.ts | 23 -- accounting-service-api/src/lib/services.ts | 25 +++ accounting-service/src/accounting.service.ts | 14 +- api-gateway/.env | 1 + .../src/accounting/accounting.module.ts | 5 +- api-gateway/src/consumer/consumer.module.ts | 5 +- api-gateway/src/delivery/delivery.module.ts | 5 +- api-gateway/src/kitchen/kitchen.module.ts | 5 +- api-gateway/src/order/order.controller.ts | 33 ++- api-gateway/src/order/order.module.ts | 5 +- .../src/restaurant/restaurant.module.ts | 5 +- bun.lockb | Bin 412808 -> 413160 bytes common/src/index.ts | 1 + common/src/lib/entities.ts | 14 +- common/src/lib/errors.ts | 1 + common/src/lib/repository.ts | 55 ++--- consumer-service-api/src/index.ts | 3 +- consumer-service-api/src/lib/dtos.ts | 9 + consumer-service-api/src/lib/entities.ts | 14 +- consumer-service-api/src/lib/service.ts | 22 -- consumer-service-api/src/lib/services.ts | 18 ++ consumer-service/src/consumer.service.ts | 15 +- delivery-service-api/src/index.ts | 2 +- .../src/lib/{service.ts => services.ts} | 8 - kitchen-service-api/src/index.ts | 2 +- kitchen-service-api/src/lib/entities.ts | 18 +- kitchen-service-api/src/lib/service.ts | 19 -- kitchen-service-api/src/lib/services.ts | 22 ++ order-service-api/src/index.ts | 4 +- order-service-api/src/lib/dtos.ts | 5 + order-service-api/src/lib/entities.ts | 208 +++++++++++++++++- order-service-api/src/lib/replies.ts | 20 ++ order-service-api/src/lib/sagas.ts | 44 ++++ order-service-api/src/lib/service.ts | 15 -- order-service-api/src/lib/services.ts | 25 +++ order-service/src/main.ts | 16 +- order-service/src/order.service.ts | 39 +++- order-service/src/sagas/cancel-order.saga.ts | 70 ++++++ order-service/src/sagas/create-order.saga.ts | 85 +++++++ order-service/src/sagas/index.ts | 2 + order-service/src/sagas/revise-order.saga.ts | 0 package.json | 3 +- restaurant-service-api/src/index.ts | 4 +- .../src/lib/{service.ts => services.ts} | 8 - restaurant-service/src/restaurant.service.ts | 1 - 47 files changed, 723 insertions(+), 186 deletions(-) delete mode 100644 accounting-service-api/src/lib/service.ts create mode 100644 accounting-service-api/src/lib/services.ts create mode 100644 common/src/lib/errors.ts create mode 100644 consumer-service-api/src/lib/dtos.ts delete mode 100644 consumer-service-api/src/lib/service.ts create mode 100644 consumer-service-api/src/lib/services.ts rename delivery-service-api/src/lib/{service.ts => services.ts} (56%) delete mode 100644 kitchen-service-api/src/lib/service.ts create mode 100644 kitchen-service-api/src/lib/services.ts create mode 100644 order-service-api/src/lib/dtos.ts create mode 100644 order-service-api/src/lib/replies.ts create mode 100644 order-service-api/src/lib/sagas.ts delete mode 100644 order-service-api/src/lib/service.ts create mode 100644 order-service-api/src/lib/services.ts create mode 100644 order-service/src/sagas/cancel-order.saga.ts create mode 100644 order-service/src/sagas/create-order.saga.ts create mode 100644 order-service/src/sagas/index.ts create mode 100644 order-service/src/sagas/revise-order.saga.ts rename restaurant-service-api/src/lib/{service.ts => services.ts} (65%) diff --git a/accounting-service-api/src/index.ts b/accounting-service-api/src/index.ts index f2ef1f5..4062db6 100644 --- a/accounting-service-api/src/index.ts +++ b/accounting-service-api/src/index.ts @@ -1,2 +1,2 @@ export * from './lib/entities'; -export * from './lib/service'; +export * from './lib/services'; diff --git a/accounting-service-api/src/lib/entities.ts b/accounting-service-api/src/lib/entities.ts index d49ecb3..065f603 100644 --- a/accounting-service-api/src/lib/entities.ts +++ b/accounting-service-api/src/lib/entities.ts @@ -1,13 +1,8 @@ -import { entity, JSONEntity, JSONPartial, uuid, UUID } from '@deepkit/type'; - -import { Consumer } from '@ftgo/consumer-service-api'; +import { entity, uuid, UUID } from '@deepkit/type'; @entity.name('account') export class Account { readonly id: UUID = uuid(); - readonly consumerId: Consumer['id']; - static create(data: JSONPartial): Account { - return Object.assign(new Account(), data); - } + constructor(readonly consumerId: UUID) {} } diff --git a/accounting-service-api/src/lib/service.ts b/accounting-service-api/src/lib/service.ts deleted file mode 100644 index a065051..0000000 --- a/accounting-service-api/src/lib/service.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { RestateClient, RestateService } from 'deepkit-restate'; -import { FactoryProvider } from '@deepkit/injector'; -import { typeOf } from '@deepkit/type'; - -import { Consumer } from '@ftgo/consumer-service-api'; - -export interface AccountingServiceHandlers { - // Only required for handlers that need to be invoked directly using a Restate client - createAccount(consumer: Consumer): Promise; -} - -export type AccountingServiceApi = RestateService< - 'Accounting', - AccountingServiceHandlers ->; - -export function provideAccountingServiceApi(): FactoryProvider { - return { - provide: typeOf(), - useFactory: (restate: RestateClient) => - restate.service(), - }; -} diff --git a/accounting-service-api/src/lib/services.ts b/accounting-service-api/src/lib/services.ts new file mode 100644 index 0000000..bdde894 --- /dev/null +++ b/accounting-service-api/src/lib/services.ts @@ -0,0 +1,25 @@ +import { RestateService } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; + +import { Consumer } from '@ftgo/consumer-service-api'; +import { Money } from '@ftgo/common'; + +export interface AccountingServiceHandlers { + // Only required for handlers that need to be invoked directly using a Restate client + createAccount(consumer: Consumer): Promise; + authorize( + consumerId: UUID, + orderId: UUID, + orderTotal: Money, + ): Promise; + reverseAuthorization( + consumerId: UUID, + orderId: UUID, + orderTotal: Money, + ): Promise; +} + +export type AccountingServiceApi = RestateService< + 'Accounting', + AccountingServiceHandlers +>; diff --git a/accounting-service/src/accounting.service.ts b/accounting-service/src/accounting.service.ts index c88619d..ebd8c12 100644 --- a/accounting-service/src/accounting.service.ts +++ b/accounting-service/src/accounting.service.ts @@ -1,7 +1,10 @@ import { restate, RestateServiceContext } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; import { Consumer, KafkaConsumerTopic } from '@ftgo/consumer-service-api'; +import { Money } from '@ftgo/common'; import { + Account, AccountingServiceApi, AccountingServiceHandlers, } from '@ftgo/accounting-service-api'; @@ -10,11 +13,18 @@ import { AccountRepository } from './account.repository'; @restate.service() export class AccountingService implements AccountingServiceHandlers { - constructor(private readonly accountRepository: AccountRepository) {} + constructor(private readonly account: AccountRepository) {} // @ts-ignore @(restate.kafka().handler()) async createAccount(consumer: Consumer): Promise { - await this.accountRepository.create({ consumerId: consumer.id }); + await this.account.create(consumer.id); } + + @restate.handler() + async authorize( + consumerId: UUID, + orderId: UUID, + orderTotal: Money, + ): Promise {} } diff --git a/api-gateway/.env b/api-gateway/.env index b32315e..5fd04fa 100644 --- a/api-gateway/.env +++ b/api-gateway/.env @@ -1,2 +1,3 @@ # Framework SERVER_PORT=3000 +SERVER_DEBUG=true diff --git a/api-gateway/src/accounting/accounting.module.ts b/api-gateway/src/accounting/accounting.module.ts index 5075fe5..0eaf2e9 100644 --- a/api-gateway/src/accounting/accounting.module.ts +++ b/api-gateway/src/accounting/accounting.module.ts @@ -1,10 +1,11 @@ import { createModule } from '@deepkit/app'; +import { provideRestateServiceProxy } from 'deepkit-restate'; -import { provideAccountingServiceApi } from '@ftgo/accounting-service-api'; +import { AccountingServiceApi } from '@ftgo/accounting-service-api'; import { AccountingController } from './accounting.controller'; export class AccountingModule extends createModule({ controllers: [AccountingController], - providers: [provideAccountingServiceApi()], + providers: [provideRestateServiceProxy()], }) {} diff --git a/api-gateway/src/consumer/consumer.module.ts b/api-gateway/src/consumer/consumer.module.ts index 7f4d2e7..2fbb02e 100644 --- a/api-gateway/src/consumer/consumer.module.ts +++ b/api-gateway/src/consumer/consumer.module.ts @@ -1,10 +1,11 @@ import { createModule } from '@deepkit/app'; +import { provideRestateServiceProxy } from 'deepkit-restate'; -import { provideConsumerServiceApi } from '@ftgo/consumer-service-api'; +import { ConsumerServiceApi } from '@ftgo/consumer-service-api'; import { ConsumerController } from './consumer.controller'; export class ConsumerModule extends createModule({ controllers: [ConsumerController], - providers: [provideConsumerServiceApi()], + providers: [provideRestateServiceProxy()], }) {} diff --git a/api-gateway/src/delivery/delivery.module.ts b/api-gateway/src/delivery/delivery.module.ts index 9c6b2d2..30df1e5 100644 --- a/api-gateway/src/delivery/delivery.module.ts +++ b/api-gateway/src/delivery/delivery.module.ts @@ -1,10 +1,11 @@ import { createModule } from '@deepkit/app'; +import { provideRestateServiceProxy } from 'deepkit-restate'; -import { provideDeliveryServiceApi } from '@ftgo/delivery-service-api'; +import { DeliveryServiceApi } from '@ftgo/delivery-service-api'; import { DeliveryController } from './delivery.controller'; export class DeliveryModule extends createModule({ controllers: [DeliveryController], - providers: [provideDeliveryServiceApi()], + providers: [provideRestateServiceProxy()], }) {} diff --git a/api-gateway/src/kitchen/kitchen.module.ts b/api-gateway/src/kitchen/kitchen.module.ts index a5050dc..a1cf157 100644 --- a/api-gateway/src/kitchen/kitchen.module.ts +++ b/api-gateway/src/kitchen/kitchen.module.ts @@ -1,10 +1,11 @@ import { createModule } from '@deepkit/app'; +import { provideRestateServiceProxy } from 'deepkit-restate'; -import { provideKitchenServiceApi } from '@ftgo/kitchen-service-api'; +import { KitchenServiceApi } from '@ftgo/kitchen-service-api'; import { KitchenController } from './kitchen.controller'; export class KitchenModule extends createModule({ controllers: [KitchenController], - providers: [provideKitchenServiceApi()], + providers: [provideRestateServiceProxy()], }) {} diff --git a/api-gateway/src/order/order.controller.ts b/api-gateway/src/order/order.controller.ts index 38bab8e..4a407d1 100644 --- a/api-gateway/src/order/order.controller.ts +++ b/api-gateway/src/order/order.controller.ts @@ -1,4 +1,33 @@ -import { http } from '@deepkit/http'; +import { http, HttpBody, HttpNotFoundError, HttpQuery } from '@deepkit/http'; +import { RestateClient } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; + +import { + CreateOrderRequest, + CreateOrderResponse, + OrderServiceApi, +} from '@ftgo/order-service-api'; @http.controller('order') -export class OrderController {} +export class OrderController { + constructor( + private readonly client: RestateClient, + private readonly service: OrderServiceApi, + ) {} + + @http.POST('') + async create( + request: HttpBody, + ): Promise {} + + @http.GET(':id') + async get(id: UUID) { + try { + } catch { + throw new HttpNotFoundError(); + } + } + + @http.POST('cancel/:id') + async cancel(id: UUID) {} +} diff --git a/api-gateway/src/order/order.module.ts b/api-gateway/src/order/order.module.ts index d40c558..a1b74f7 100644 --- a/api-gateway/src/order/order.module.ts +++ b/api-gateway/src/order/order.module.ts @@ -1,10 +1,11 @@ import { createModule } from '@deepkit/app'; +import { provideRestateServiceProxy } from 'deepkit-restate'; -import { provideOrderServiceApi } from '@ftgo/order-service-api'; +import { OrderServiceApi } from '@ftgo/order-service-api'; import { OrderController } from './order.controller'; export class OrderModule extends createModule({ controllers: [OrderController], - providers: [provideOrderServiceApi()], + providers: [provideRestateServiceProxy()], }) {} diff --git a/api-gateway/src/restaurant/restaurant.module.ts b/api-gateway/src/restaurant/restaurant.module.ts index a5b3c26..f44b71b 100644 --- a/api-gateway/src/restaurant/restaurant.module.ts +++ b/api-gateway/src/restaurant/restaurant.module.ts @@ -1,10 +1,11 @@ import { createModule } from '@deepkit/app'; +import { provideRestateServiceProxy } from 'deepkit-restate'; -import { provideRestaurantServiceApi } from '@ftgo/restaurant-service-api'; +import { RestaurantServiceApi } from '@ftgo/restaurant-service-api'; import { RestaurantController } from './restaurant.controller'; export class RestaurantModule extends createModule({ controllers: [RestaurantController], - providers: [provideRestaurantServiceApi()], + providers: [provideRestateServiceProxy()], }) {} diff --git a/bun.lockb b/bun.lockb index 558ccf8febf875d9ab95030d3f98aa99a54a10dd..2daaa7eef36d08e1892d14d6ebed803655eb918b 100755 GIT binary patch delta 73125 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 delta 73415 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

H#oK@Q!;}Z3ldumRk|~t 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 H-NLNJVe7=&8vQG0%?B=RW?#B1z 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 - entity: ClassType & { create(data: JSONPartial): T }, -) { +export function RestateRepository(entity: ClassType) { return class Repository { constructor( readonly contextStorage: RestateContextStorage, @@ -28,45 +17,45 @@ export function RestateRepository( ) {} get #ctx(): Pick { - return ( - this.contextStorage.getStore() || { - run: async (action: RunAction) => action(), - } - ); + return this.contextStorage.getStore()!; } - // async findOne(filter: DatabaseQueryModel['filter']): Promise { + // async findOne(filter: DatabaseQueryModel['filter']): Promise { // return await this.database.query(entity).filter(filter).findOne(); // } async delete( - filter: DatabaseQueryModel['filter'], - ): Promise> { - return await this.#ctx.run>(() => + filter: DatabaseQueryModel['filter'], + ): Promise> { + return await this.#ctx.run>(() => this.database.query(entity).filter(filter).deleteOne(), ); } - // async find(filter: DatabaseQueryModel['filter']): Promise { + // async find(filter: DatabaseQueryModel['filter']): Promise { // return await this.database.query(entity).filter(filter).find(); // } - async find(filter: DatabaseQueryModel['filter']): Promise { + async find(filter: DatabaseQueryModel['filter']): Promise { return await this.database.query(entity).filter(filter).findOne(); } - async update( - filter: DatabaseQueryModel['filter'], - changes: ChangesInterface | DeepPartial, - ): Promise> { - return await this.#ctx.run>(() => + async persist(entity: E): Promise { + await this.#ctx.run(() => this.database.persist(entity)); + } + + async patch( + filter: DatabaseQueryModel['filter'], + changes: ChangesInterface | DeepPartial, + ): Promise> { + return await this.#ctx.run>(() => this.database.query(entity).filter(filter).patchOne(changes), ); } - async create(data: JSONPartial): Promise { - return await this.#ctx.run(async () => { - const et = entity.create(data); + async create(...args: ConstructorParameters>): Promise { + return await this.#ctx.run(async () => { + const et = new entity(...args); await this.database.persist(et); return et; }); diff --git a/consumer-service-api/src/index.ts b/consumer-service-api/src/index.ts index 6908d38..edebf30 100644 --- a/consumer-service-api/src/index.ts +++ b/consumer-service-api/src/index.ts @@ -1,3 +1,4 @@ +export * from './lib/dtos'; export * from './lib/entities'; -export * from './lib/service'; +export * from './lib/services'; export * from './lib/topics'; diff --git a/consumer-service-api/src/lib/dtos.ts b/consumer-service-api/src/lib/dtos.ts new file mode 100644 index 0000000..f002b20 --- /dev/null +++ b/consumer-service-api/src/lib/dtos.ts @@ -0,0 +1,9 @@ +import { UUID } from '@deepkit/type'; + +import { Money } from '@ftgo/common'; + +export interface ValidateOrderRequest { + readonly consumerId: UUID; + readonly orderId: UUID; + readonly orderTotal: Money; +} diff --git a/consumer-service-api/src/lib/entities.ts b/consumer-service-api/src/lib/entities.ts index 7856f62..5c0e428 100644 --- a/consumer-service-api/src/lib/entities.ts +++ b/consumer-service-api/src/lib/entities.ts @@ -1,20 +1,10 @@ -import { - entity, - JSONEntity, - JSONPartial, - PrimaryKey, - uuid, - UUID, -} from '@deepkit/type'; +import { entity, PrimaryKey, uuid, UUID } from '@deepkit/type'; import { PersonName } from '@ftgo/common'; @entity.name('consumer') export class Consumer { readonly id: UUID & PrimaryKey = uuid(); - readonly name: PersonName; - static create(data: JSONPartial) { - return Object.assign(new Consumer(), data); - } + constructor(readonly name: PersonName) {} } diff --git a/consumer-service-api/src/lib/service.ts b/consumer-service-api/src/lib/service.ts deleted file mode 100644 index 035f984..0000000 --- a/consumer-service-api/src/lib/service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { RestateClient, RestateService } from 'deepkit-restate'; -import { typeOf, UUID } from '@deepkit/type'; - -import { PersonName } from '@ftgo/common'; -import { FactoryProvider } from '@deepkit/injector'; - -export interface ConsumerServiceHandlers { - create(name: PersonName): Promise; -} - -export type ConsumerServiceApi = RestateService< - 'Consumer', - ConsumerServiceHandlers ->; - -export function provideConsumerServiceApi(): FactoryProvider { - return { - provide: typeOf(), - useFactory: (restate: RestateClient) => - restate.service(), - }; -} diff --git a/consumer-service-api/src/lib/services.ts b/consumer-service-api/src/lib/services.ts new file mode 100644 index 0000000..3233c27 --- /dev/null +++ b/consumer-service-api/src/lib/services.ts @@ -0,0 +1,18 @@ +import { RestateService } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; + +import { Money, PersonName } from '@ftgo/common'; + +export interface ConsumerServiceHandlers { + create(name: PersonName): Promise; + validateOrder( + consumerId: UUID, + orderId: UUID, + orderTotal: Money, + ): Promise; +} + +export type ConsumerServiceApi = RestateService< + 'Consumer', + ConsumerServiceHandlers +>; diff --git a/consumer-service/src/consumer.service.ts b/consumer-service/src/consumer.service.ts index d4d95f9..202d155 100644 --- a/consumer-service/src/consumer.service.ts +++ b/consumer-service/src/consumer.service.ts @@ -1,8 +1,8 @@ -import { restate, RestateServiceContext } from 'deepkit-restate'; +import { restate } from 'deepkit-restate'; import { RestateKafkaProducer } from 'deepkit-restate/kafka'; import { UUID } from '@deepkit/type'; -import { PersonName } from '@ftgo/common'; +import { Money, PersonName } from '@ftgo/common'; import { Consumer, ConsumerServiceApi, @@ -21,8 +21,17 @@ export class ConsumerService implements ConsumerServiceHandlers { @restate.handler() async create(name: PersonName): Promise { - const consumer = (await this.consumer.create({ name })) as Consumer; + const consumer = (await this.consumer.create(name)) as Consumer; await this.kafka.produce([consumer]); return consumer.id; } + + @restate.handler() + validateOrder( + consumerId: UUID, + orderId: UUID, + orderTotal: Money, + ): Promise { + return Promise.resolve(undefined); + } } diff --git a/delivery-service-api/src/index.ts b/delivery-service-api/src/index.ts index f2ef1f5..4062db6 100644 --- a/delivery-service-api/src/index.ts +++ b/delivery-service-api/src/index.ts @@ -1,2 +1,2 @@ export * from './lib/entities'; -export * from './lib/service'; +export * from './lib/services'; diff --git a/delivery-service-api/src/lib/service.ts b/delivery-service-api/src/lib/services.ts similarity index 56% rename from delivery-service-api/src/lib/service.ts rename to delivery-service-api/src/lib/services.ts index 846754d..8e4d7e0 100644 --- a/delivery-service-api/src/lib/service.ts +++ b/delivery-service-api/src/lib/services.ts @@ -9,11 +9,3 @@ export type DeliveryServiceApi = RestateService< 'Delivery', DeliveryServiceHandlers >; - -export function provideDeliveryServiceApi(): FactoryProvider { - return { - provide: typeOf(), - useFactory: (restate: RestateClient) => - restate.service(), - }; -} diff --git a/kitchen-service-api/src/index.ts b/kitchen-service-api/src/index.ts index f2ef1f5..4062db6 100644 --- a/kitchen-service-api/src/index.ts +++ b/kitchen-service-api/src/index.ts @@ -1,2 +1,2 @@ export * from './lib/entities'; -export * from './lib/service'; +export * from './lib/services'; diff --git a/kitchen-service-api/src/lib/entities.ts b/kitchen-service-api/src/lib/entities.ts index a017101..9ddb877 100644 --- a/kitchen-service-api/src/lib/entities.ts +++ b/kitchen-service-api/src/lib/entities.ts @@ -1,4 +1,4 @@ -import { entity, JSONEntity } from '@deepkit/type'; +import { entity, integer, JSONEntity, Positive, UUID } from '@deepkit/type'; @entity.name('kitchen') export class Kitchen { @@ -6,3 +6,19 @@ export class Kitchen { return new Kitchen(); } } + +@entity.name('ticket') +export class Ticket { + readonly id: UUID; +} + +@entity.name('ticket-line-item') +export class TicketLineItem { + readonly quantity: integer & Positive; + readonly menuItemId: UUID; + readonly name: string; +} + +export interface TicketDetails { + readonly lineItems: readonly TicketLineItem[]; +} diff --git a/kitchen-service-api/src/lib/service.ts b/kitchen-service-api/src/lib/service.ts deleted file mode 100644 index c370318..0000000 --- a/kitchen-service-api/src/lib/service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { RestateClient, RestateService } from 'deepkit-restate'; -import { typeOf, UUID } from '@deepkit/type'; - -import { FactoryProvider } from '@deepkit/injector'; - -export interface KitchenServiceHandlers {} - -export type KitchenServiceApi = RestateService< - 'Kitchen', - KitchenServiceHandlers ->; - -export function provideKitchenServiceApi(): FactoryProvider { - return { - provide: typeOf(), - useFactory: (restate: RestateClient) => - restate.service(), - }; -} diff --git a/kitchen-service-api/src/lib/services.ts b/kitchen-service-api/src/lib/services.ts new file mode 100644 index 0000000..b023e15 --- /dev/null +++ b/kitchen-service-api/src/lib/services.ts @@ -0,0 +1,22 @@ +import { RestateService } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; + +import { Ticket, TicketDetails } from './entities'; + +export interface KitchenServiceHandlers { + createTicket( + restaurantId: UUID, + orderId: UUID, + details: TicketDetails, + ): Promise; + confirmCreateTicket(ticketId: UUID): Promise; + cancelTicket(orderId: UUID): Promise; + beginCancelTicket(restaurantId: UUID, orderId: UUID): Promise; + undoBeginCancelTicket(restaurantId: UUID, orderId: UUID): Promise; + confirmCancelTicket(restaurantId: UUID, orderId: UUID): Promise; +} + +export type KitchenServiceApi = RestateService< + 'Kitchen', + KitchenServiceHandlers +>; diff --git a/order-service-api/src/index.ts b/order-service-api/src/index.ts index f2ef1f5..81b60e2 100644 --- a/order-service-api/src/index.ts +++ b/order-service-api/src/index.ts @@ -1,2 +1,4 @@ +export * from './lib/dtos'; export * from './lib/entities'; -export * from './lib/service'; +export * from './lib/sagas'; +export * from './lib/services'; diff --git a/order-service-api/src/lib/dtos.ts b/order-service-api/src/lib/dtos.ts new file mode 100644 index 0000000..94d66a7 --- /dev/null +++ b/order-service-api/src/lib/dtos.ts @@ -0,0 +1,5 @@ +export interface CreateOrderRequest {} + +export interface CreateOrderResponse {} + +export interface GetOrderResponse {} diff --git a/order-service-api/src/lib/entities.ts b/order-service-api/src/lib/entities.ts index 5643a45..25a42ed 100644 --- a/order-service-api/src/lib/entities.ts +++ b/order-service-api/src/lib/entities.ts @@ -1,8 +1,210 @@ -import { entity, JSONEntity } from '@deepkit/type'; +import { + Embedded, + entity, + integer, + JSONEntity, + JSONPartial, + Positive, + PrimaryKey, + UUID, +} from '@deepkit/type'; +import { Writable } from 'type-fest'; + +import { + Address, + Money, + RevisedOrderLineItem, + UnsupportedStateTransitionException, +} from '@ftgo/common'; +import { + OrderMinimumNotMetException, + OrderRevised, + OrderRevisionProposed, +} from './replies'; + +export enum OrderState { + APPROVAL_PENDING = 'APPROVAL_PENDING', + APPROVED = 'APPROVED', + REJECTED = 'REJECTED', + CANCEL_PENDING = 'CANCEL_PENDING', + CANCELLED = 'CANCELLED', + REVISION_PENDING = 'REVISION_PENDING', +} @entity.name('order') export class Order { - static create(data: JSONEntity): Order { - return new Order(); + readonly id: UUID & PrimaryKey; + readonly state: OrderState = OrderState.APPROVAL_PENDING; + readonly paymentInformation?: Embedded; + readonly orderMinimum: Embedded = new Money(Number.MAX_SAFE_INTEGER); + + readonly consumerId: UUID; + readonly restaurantId: UUID; + readonly deliveryInformation: Embedded; + readonly lineItems: OrderLineItems; + + // constructor( + // readonly consumerId: UUID, + // readonly restaurantId: UUID, + // readonly deliveryInformation: Embedded, + // readonly lineItems: OrderLineItems, + // ) {} + + static create(data: JSONPartial): Order { + return Object.assign(new Order(), data); + // return new Order( + // data.consumerId, + // data.restaurantId, + // data.deliveryInformation, + // data.lineItems, + // ); + } + + cancel(this: Writable): void { + if (this.state !== OrderState.APPROVED) { + throw new UnsupportedStateTransitionException(this.state); + } + this.state = OrderState.CANCEL_PENDING; + } + + undoPendingCancel(this: Writable): void { + if (this.state !== OrderState.CANCEL_PENDING) { + throw new UnsupportedStateTransitionException(this.state); + } + this.state = OrderState.APPROVED; + } + + noteCancelled(this: Writable): void { + if (this.state !== OrderState.CANCEL_PENDING) { + throw new UnsupportedStateTransitionException(this.state); + } + this.state = OrderState.CANCELLED; + } + + noteApproved(this: Writable): void { + if (this.state !== OrderState.APPROVAL_PENDING) { + throw new UnsupportedStateTransitionException(this.state); + } + this.state = OrderState.APPROVED; + } + + noteRejected(this: Writable) { + if (this.state !== OrderState.APPROVAL_PENDING) { + throw new UnsupportedStateTransitionException(this.state); + } + this.state = OrderState.REJECTED; + } + + noteReversingAuthorization() {} + + revise(this: Writable, revision: OrderRevision): OrderRevisionProposed { + if (this.state !== OrderState.APPROVED) { + throw new UnsupportedStateTransitionException(this.state); + } + const change = this.lineItems.getLineItemQuantityChange(revision); + if (change.newOrderTotal.isGreaterThanOrEqual(this.orderMinimum)) { + throw new OrderMinimumNotMetException(); + } + this.state = OrderState.REVISION_PENDING; + return new OrderRevisionProposed(revision, change); + } + + rejectRevision(this: Writable): void { + if (this.state !== OrderState.REVISION_PENDING) { + throw new UnsupportedStateTransitionException(this.state); + } + this.state = OrderState.APPROVED; + } + + confirmRevision(this: Writable, revision: OrderRevision): OrderRevised { + if (this.state !== OrderState.REVISION_PENDING) { + throw new UnsupportedStateTransitionException(this.state); + } + + if (revision.deliveryInformation) { + this.deliveryInformation = revision.deliveryInformation; + } + + if (revision.revisedOrderLineItems.length) { + this.lineItems.updateLineItems(revision); + } + + this.state = OrderState.APPROVED; + + const change = this.lineItems.getLineItemQuantityChange(revision); + return new OrderRevised(revision, change); + } +} + +// @entity.name('order-revision') +export class OrderRevision { + constructor( + readonly revisedOrderLineItems: readonly RevisedOrderLineItem[], + readonly deliveryInformation?: DeliveryInformation, + ) {} +} + +export class OrderLineItems { + constructor(readonly lineItems: readonly OrderLineItem[]) {} + + changeToOrderTotal(orderRevision: OrderRevision) {} + + find(menuItemId: UUID): OrderLineItem { + return this.lineItems.find(lineItem => lineItem.menuItemId === menuItemId); + } + + updateLineItems(orderRevision: OrderRevision) {} + + getOrderTotal(): Money { + return this.lineItems + .map(lineItem => lineItem.getTotal()) + .reduce((left, right) => left.add(right), Money.ZERO); + } + + getLineItemQuantityChange( + orderRevision: OrderRevision, + ): LineItemQuantityChange {} +} + +export class LineItemQuantityChange { + constructor( + readonly currentOrderTotal: Money, + readonly newOrderTotal: Money, + readonly delta: Money, + ) {} +} + +@entity.name('order-line-item') +export class OrderLineItem { + readonly quantity: integer & Positive; + readonly menuItemId: UUID; + readonly name: string; + readonly price: Money; + + deltaForChangedQuantity(newQuantity: integer & Positive): Money { + return this.price.multiply(newQuantity - this.quantity); + } + + getTotal(): Money { + return this.price.multiply(this.quantity); } } + +export interface OrderDetails { + readonly consumerId: UUID; + readonly restaurantId: UUID; + readonly lineItems: readonly OrderLineItem[]; + readonly orderTotal: Money; +} + +export interface DeliveryInformation { + readonly address: Address; + readonly time: Date; +} + +export interface PaymentInformation {} + +// @entity.name('delivery-information') +// export class DeliveryInformation { +// constructor(readonly address: Address, readonly time: Date) {} +// } diff --git a/order-service-api/src/lib/replies.ts b/order-service-api/src/lib/replies.ts new file mode 100644 index 0000000..c95509d --- /dev/null +++ b/order-service-api/src/lib/replies.ts @@ -0,0 +1,20 @@ +import { entity } from '@deepkit/type'; + +import { LineItemQuantityChange, OrderRevision } from './entities'; + +@entity.name('@error/order-minimum-not-met') +export class OrderMinimumNotMetException extends Error {} + +export class OrderRevisionProposed { + constructor( + readonly revision: OrderRevision, + readonly change: LineItemQuantityChange, + ) {} +} + +export class OrderRevised { + constructor( + readonly revision: OrderRevision, + readonly change: LineItemQuantityChange, + ) {} +} diff --git a/order-service-api/src/lib/sagas.ts b/order-service-api/src/lib/sagas.ts new file mode 100644 index 0000000..8631870 --- /dev/null +++ b/order-service-api/src/lib/sagas.ts @@ -0,0 +1,44 @@ +import { UUID } from '@deepkit/type'; +import { RestateSaga } from 'deepkit-restate'; + +import { Money } from '@ftgo/common'; + +import { OrderDetails, OrderRevision } from './entities'; + +export class CreateOrderSagaData { + readonly orderId: UUID; + readonly orderDetails: OrderDetails; + readonly ticketId?: UUID; +} + +export type CreateOrderSagaApi = RestateSaga< + 'create-order', + CreateOrderSagaData +>; + +export class CancelOrderSagaData { + readonly orderId: UUID; + readonly reverseRequestId: UUID; + readonly restaurantId: UUID; + readonly consumerId: UUID; + readonly orderTotal: Money; +} + +export type CancelOrderSagaApi = RestateSaga< + 'cancel-order', + CancelOrderSagaData +>; + +export class ReviseOrderSagaData { + readonly orderRevision: OrderRevision; + readonly orderId: UUID; + readonly expectedVersion: UUID; + readonly restaurantId: UUID; + readonly revisedOrderTotal: Money; + readonly consumerId: UUID; +} + +export type ReviseOrderSagaApi = RestateSaga< + 'revise-order', + ReviseOrderSagaData +>; diff --git a/order-service-api/src/lib/service.ts b/order-service-api/src/lib/service.ts deleted file mode 100644 index ebb4c73..0000000 --- a/order-service-api/src/lib/service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { RestateClient, RestateService } from 'deepkit-restate'; -import { typeOf, UUID } from '@deepkit/type'; - -import { FactoryProvider } from '@deepkit/injector'; - -export interface OrderServiceHandlers {} - -export type OrderServiceApi = RestateService<'Order', OrderServiceHandlers>; - -export function provideOrderServiceApi(): FactoryProvider { - return { - provide: typeOf(), - useFactory: (restate: RestateClient) => restate.service(), - }; -} diff --git a/order-service-api/src/lib/services.ts b/order-service-api/src/lib/services.ts new file mode 100644 index 0000000..3691b10 --- /dev/null +++ b/order-service-api/src/lib/services.ts @@ -0,0 +1,25 @@ +import { RestateService } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; +import { ItemNotFound } from '@deepkit/orm'; + +import { Order } from './entities'; +import { CreateOrderRequest } from './dtos'; +import { OrderMinimumNotMetException } from './replies'; + +export interface OrderServiceHandlers { + create(request: CreateOrderRequest): Promise; + get(id: UUID): Promise; + beginCancel(id: UUID): Promise; + undoBeginCancel(id: UUID): Promise; + undoCancel(id: UUID): Promise; + cancel(id: UUID): Promise; + confirmCancel(id: UUID): Promise; + reject(id: UUID): Promise; + approve(id: UUID): Promise; +} + +export type OrderServiceApi = RestateService< + 'Order', + OrderServiceHandlers, + [ItemNotFound, OrderMinimumNotMetException] +>; diff --git a/order-service/src/main.ts b/order-service/src/main.ts index fb653c0..dd00717 100644 --- a/order-service/src/main.ts +++ b/order-service/src/main.ts @@ -1,19 +1,29 @@ import { App } from '@deepkit/app'; import { FrameworkModule } from '@deepkit/framework'; -import { RestateModule } from 'deepkit-restate'; +import { provideRestateServiceProxy, RestateModule } from 'deepkit-restate'; +import { KitchenServiceApi } from '@ftgo/kitchen-service-api'; +import { ConsumerServiceApi } from '@ftgo/consumer-service-api'; +import { AccountingServiceApi } from '@ftgo/accounting-service-api'; import { Order } from '@ftgo/order-service-api'; import { provideDatabase } from '@ftgo/common'; import { OrderServiceConfig } from './config'; import { OrderService } from './order.service'; import { OrderRepository } from './order.repository'; +import { CancelOrderSaga, CreateOrderSaga } from './sagas'; void new App({ config: OrderServiceConfig, imports: [new FrameworkModule(), new RestateModule()], - controllers: [OrderService], - providers: [provideDatabase([Order]), OrderRepository], + controllers: [OrderService, CreateOrderSaga, CancelOrderSaga], + providers: [ + provideDatabase([Order]), + OrderRepository, + provideRestateServiceProxy(), + provideRestateServiceProxy(), + provideRestateServiceProxy(), + ], }) .setup((module, config: OrderServiceConfig) => { module diff --git a/order-service/src/order.service.ts b/order-service/src/order.service.ts index 5232be8..ac5cabc 100644 --- a/order-service/src/order.service.ts +++ b/order-service/src/order.service.ts @@ -1,4 +1,5 @@ -import { restate, RestateServiceContext } from 'deepkit-restate'; +import { restate } from 'deepkit-restate'; +import { UUID } from '@deepkit/type'; import { KafkaRestaurantCreatedTopic, @@ -7,13 +8,18 @@ import { RestaurantMenu, } from '@ftgo/restaurant-service-api'; import { + CreateOrderRequest, Order, OrderServiceApi, OrderServiceHandlers, } from '@ftgo/order-service-api'; +import { OrderRepository } from './order.repository'; + @restate.service() export class OrderService implements OrderServiceHandlers { + constructor(private readonly order: OrderRepository) {} + // @ts-ignore @(restate.kafka().handler()) async createMenu(restaurant: Restaurant): Promise {} @@ -21,4 +27,35 @@ export class OrderService implements OrderServiceHandlers { // @ts-ignore @(restate.kafka().handler()) async reviseMenu(menu: RestaurantMenu): Promise {} + + @restate.handler() + async create(request: CreateOrderRequest) {} + + @restate.handler() + async cancel(id: UUID): Promise {} + + @restate.handler() + async get(id: UUID): Promise { + return await this.order.find({ id }); + } + + async approve(id: UUID): Promise { + return Promise.resolve(undefined); + } + + async reject(id: UUID): Promise { + return Promise.resolve(undefined); + } + + @restate.handler() + async beginCancel(id: UUID): Promise { + const order = await this.order.find({ id }); + order.cancel(); + await this.order.persist(order); + return order; + } + + undoCancel(id: UUID): Promise { + return Promise.resolve(undefined); + } } diff --git a/order-service/src/sagas/cancel-order.saga.ts b/order-service/src/sagas/cancel-order.saga.ts new file mode 100644 index 0000000..783e3d4 --- /dev/null +++ b/order-service/src/sagas/cancel-order.saga.ts @@ -0,0 +1,70 @@ +import { restate, Saga } from 'deepkit-restate'; + +import { KitchenServiceApi } from '@ftgo/kitchen-service-api'; +import { AccountingServiceApi } from '@ftgo/accounting-service-api'; +import { + CancelOrderSagaApi, + CancelOrderSagaData, + OrderServiceApi, +} from '@ftgo/order-service-api'; + +@restate.saga() +export class CancelOrderSaga extends Saga { + readonly definition = this.step() + .invoke(this.beginCancelOrder) + .compensate(this.undoBeginCancelOrder) + .step() + .invoke(this.beginCancelTicket) + .compensate(this.undoBeginCancelTicket) + .step() + .invoke(this.reverseAuthorization) + .step() + .invoke(this.confirmCancelTicket) + .step() + .invoke(this.confirmCancelOrder) + .build(); + + constructor( + private readonly order: OrderServiceApi, + private readonly kitchen: KitchenServiceApi, + private readonly accounting: AccountingServiceApi, + ) { + super(); + } + + beginCancelOrder({ orderId }: CancelOrderSagaData) { + return this.order.beginCancel(orderId); + } + + undoBeginCancelOrder({ orderId }: CancelOrderSagaData) { + return this.order.undoBeginCancel(orderId); + } + + beginCancelTicket({ restaurantId, orderId }: CancelOrderSagaData) { + return this.kitchen.beginCancelTicket(restaurantId, orderId); + } + + undoBeginCancelTicket({ restaurantId, orderId }: CancelOrderSagaData) { + return this.kitchen.undoBeginCancelTicket(restaurantId, orderId); + } + + reverseAuthorization({ + consumerId, + orderId, + orderTotal, + }: CancelOrderSagaData) { + return this.accounting.reverseAuthorization( + consumerId, + orderId, + orderTotal, + ); + } + + confirmCancelTicket({ restaurantId, orderId }: CancelOrderSagaData) { + return this.kitchen.confirmCancelTicket(restaurantId, orderId); + } + + confirmCancelOrder({ orderId }: CancelOrderSagaData) { + return this.order.confirmCancel(orderId); + } +} diff --git a/order-service/src/sagas/create-order.saga.ts b/order-service/src/sagas/create-order.saga.ts new file mode 100644 index 0000000..a6caa33 --- /dev/null +++ b/order-service/src/sagas/create-order.saga.ts @@ -0,0 +1,85 @@ +import { restate, Saga } from 'deepkit-restate'; +import { cast, UUID } from '@deepkit/type'; + +import { ConsumerServiceApi } from '@ftgo/consumer-service-api'; +import { AccountingServiceApi } from '@ftgo/accounting-service-api'; +import { + KitchenServiceApi, + Ticket, + TicketDetails, +} from '@ftgo/kitchen-service-api'; +import { + CreateOrderSagaApi, + CreateOrderSagaData, + OrderServiceApi, +} from '@ftgo/order-service-api'; + +@restate.saga() +export class CreateOrderSaga extends Saga { + readonly definition = this.step() + .compensate(this.reject) + .step() + .invoke(this.validate) + .step() + .invoke(this.createTicket) + .onReply(this.handleTicketCreated) + .compensate(this.cancelTicket) + .step() + .invoke(this.authorize) + .step() + .invoke(this.confirmCreateTicket) + .step() + .invoke(this.approve) + .build(); + + constructor( + private readonly consumer: ConsumerServiceApi, + private readonly order: OrderServiceApi, + private readonly kitchen: KitchenServiceApi, + private readonly accounting: AccountingServiceApi, + ) { + super(); + } + + reject({ orderId }: CreateOrderSagaData) { + return this.order.reject(orderId); + } + + validate({ + orderDetails: { consumerId, orderTotal }, + orderId, + }: CreateOrderSagaData) { + return this.consumer.validateOrder(consumerId, orderId, orderTotal); + } + + createTicket({ + orderDetails: { lineItems, restaurantId }, + orderId, + }: CreateOrderSagaData) { + const details = cast({ lineItems }); + return this.kitchen.createTicket(restaurantId, orderId, details); + } + + handleTicketCreated(data: CreateOrderSagaData, ticket: Ticket) { + Object.assign(data, { ticketId: ticket.id }); + } + + cancelTicket({ orderId }: CreateOrderSagaData) { + return this.kitchen.cancelTicket(orderId); + } + + authorize({ + orderDetails: { consumerId, orderTotal }, + orderId, + }: CreateOrderSagaData) { + return this.accounting.authorize(consumerId, orderId, orderTotal); + } + + confirmCreateTicket({ ticketId }: CreateOrderSagaData) { + return this.kitchen.confirmCreateTicket(ticketId!); + } + + approve({ orderId }: CreateOrderSagaData) { + return this.order.approve(orderId); + } +} diff --git a/order-service/src/sagas/index.ts b/order-service/src/sagas/index.ts new file mode 100644 index 0000000..94e3223 --- /dev/null +++ b/order-service/src/sagas/index.ts @@ -0,0 +1,2 @@ +export * from './cancel-order.saga'; +export * from './create-order.saga'; diff --git a/order-service/src/sagas/revise-order.saga.ts b/order-service/src/sagas/revise-order.saga.ts new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index be2e269..6752d4b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@deepkit/postgres": "^1.0.1-alpha.153", "@deepkit/sql": "^1.0.1-alpha.153", "@deepkit/type": "^1.0.1-alpha.153", - "deepkit-restate": "^0.0.37", + "deepkit-restate": "^0.0.39", "kafkajs": "2.2.4", "tslib": "2.6.3" }, @@ -49,6 +49,7 @@ "jsdom": "~22.1.0", "nx": "19.5.7", "prettier": "^3.3.3", + "type-fest": "^4.24.0", "typescript": "^5.5.4", "vite": "^5.4.0", "vitest": "^2.0.5", diff --git a/restaurant-service-api/src/index.ts b/restaurant-service-api/src/index.ts index ae856c3..edebf30 100644 --- a/restaurant-service-api/src/index.ts +++ b/restaurant-service-api/src/index.ts @@ -1,4 +1,4 @@ -export * from './lib/entities'; export * from './lib/dtos'; -export * from './lib/service'; +export * from './lib/entities'; +export * from './lib/services'; export * from './lib/topics'; diff --git a/restaurant-service-api/src/lib/service.ts b/restaurant-service-api/src/lib/services.ts similarity index 65% rename from restaurant-service-api/src/lib/service.ts rename to restaurant-service-api/src/lib/services.ts index e9d0f3d..c6b73e5 100644 --- a/restaurant-service-api/src/lib/service.ts +++ b/restaurant-service-api/src/lib/services.ts @@ -13,11 +13,3 @@ export type RestaurantServiceApi = RestateService< 'Restaurant', RestaurantServiceHandlers >; - -export function provideRestaurantServiceApi(): FactoryProvider { - return { - provide: typeOf(), - useFactory: (restate: RestateClient) => - restate.service(), - }; -} diff --git a/restaurant-service/src/restaurant.service.ts b/restaurant-service/src/restaurant.service.ts index 45ec193..afa082a 100644 --- a/restaurant-service/src/restaurant.service.ts +++ b/restaurant-service/src/restaurant.service.ts @@ -20,7 +20,6 @@ export class RestaurantService implements RestaurantServiceHandlers { @restate.handler() async create(request: CreateRestaurantRequest): Promise { - // FIXME: why is type T and not Restaurant ? const restaurant = (await this.restaurant.create(request)) as Restaurant; await this.kafka.produce([restaurant]); return restaurant;