Skip to content

Commit

Permalink
Added example of read model
Browse files Browse the repository at this point in the history
Extended model by payment and charge id.
Nested api to make it more readable.
  • Loading branch information
oskardudycz committed Jun 12, 2024
1 parent 56699d7 commit f94ec1b
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 7 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import {
import type { TestEventStream } from '@event-driven-io/emmett-expressjs/dist/testing/utils';
import { randomUUID } from 'node:crypto';
import { beforeEach, describe, it } from 'node:test';
import { guestStayAccountsApi } from './api';
import {
toGuestStayAccountId,
type GuestStayAccountEvent,
} from './guestStayAccount';
} from '../guestStayAccount';
import { guestStayAccountsApi } from './api';

const doesGuestStayExist = (_guestId: string, _roomId: string, _day: Date) =>
Promise.resolve(true);
Expand Down
13 changes: 11 additions & 2 deletions src/guestStayAccounts/api.ts → src/guestStayAccounts/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type WebApiSetup,
} from '@event-driven-io/emmett-expressjs';
import { type Request, type Router } from 'express';
import { randomUUID } from 'node:crypto';
import {
checkIn,
checkOut,
Expand All @@ -25,8 +26,12 @@ import {
type CheckOut,
type RecordCharge,
type RecordPayment,
} from './businessLogic';
import { evolve, initialState, toGuestStayAccountId } from './guestStayAccount';
} from '../businessLogic';
import {
evolve,
initialState,
toGuestStayAccountId,
} from '../guestStayAccount';

export const handle = CommandHandler(evolve, initialState);

Expand Down Expand Up @@ -104,10 +109,12 @@ export const guestStayAccountsApi =
'/guests/:guestId/stays/:roomId/periods/:checkInDate/charges',
on(async (request: RecordChargeRequest) => {
const guestStayAccountId = parseGuestStayAccountId(request.params);
const chargeId = randomUUID();

const command: RecordCharge = {
type: 'RecordCharge',
data: {
chargeId,
guestStayAccountId,
amount: assertPositiveNumber(Number(request.body.amount)),
},
Expand All @@ -127,10 +134,12 @@ export const guestStayAccountsApi =
'/guests/:guestId/stays/:roomId/periods/:checkInDate/payments',
on(async (request: RecordPaymentRequest) => {
const guestStayAccountId = parseGuestStayAccountId(request.params);
const paymentId = randomUUID();

const command: RecordPayment = {
type: 'RecordPayment',
data: {
paymentId,
guestStayAccountId,
amount: assertPositiveNumber(Number(request.body.amount)),
},
Expand Down
8 changes: 6 additions & 2 deletions src/guestStayAccounts/businessLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type CheckIn = Command<
export type RecordCharge = Command<
'RecordCharge',
{
chargeId: string;
guestStayAccountId: string;
amount: number;
}
Expand All @@ -30,6 +31,7 @@ export type RecordCharge = Command<
export type RecordPayment = Command<
'RecordPayment',
{
paymentId: string;
guestStayAccountId: string;
amount: number;
}
Expand Down Expand Up @@ -69,14 +71,15 @@ export const checkIn = (
};

export const recordCharge = (
{ data: { guestStayAccountId, amount }, metadata }: RecordCharge,
{ data: { guestStayAccountId, chargeId, amount }, metadata }: RecordCharge,
state: GuestStayAccount,
): ChargeRecorded => {
assertIsOpened(state);

return {
type: 'ChargeRecorded',
data: {
chargeId,
guestStayAccountId,
amount: amount,
recordedAt: metadata?.now ?? new Date(),
Expand All @@ -85,14 +88,15 @@ export const recordCharge = (
};

export const recordPayment = (
{ data: { guestStayAccountId, amount }, metadata }: RecordPayment,
{ data: { guestStayAccountId, paymentId, amount }, metadata }: RecordPayment,
state: GuestStayAccount,
): PaymentRecorded => {
assertIsOpened(state);

return {
type: 'PaymentRecorded',
data: {
paymentId,
guestStayAccountId,
amount: amount,
recordedAt: metadata?.now ?? new Date(),
Expand Down
25 changes: 25 additions & 0 deletions src/guestStayAccounts/businessLogic.unit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ void describe('Guest Stay Account', () => {
const roomId = randomUUID();
const guestStayAccountId = toGuestStayAccountId(guestId, roomId, now);
const amount = Math.random() * 100;
const chargeId = randomUUID();
const nextChargeId = randomUUID();
const paymentId = randomUUID();
const nextPaymentId = randomUUID();

void describe('When not existing', () => {
const notExistingAccount: GuestStayAccountEvent[] = [];
Expand Down Expand Up @@ -57,6 +61,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordCharge',
data: {
chargeId,
guestStayAccountId,
amount,
},
Expand All @@ -71,6 +76,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordPayment',
data: {
paymentId,
guestStayAccountId,
amount,
},
Expand Down Expand Up @@ -134,6 +140,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordCharge',
data: {
chargeId,
guestStayAccountId,
amount,
},
Expand All @@ -143,6 +150,7 @@ void describe('Guest Stay Account', () => {
{
type: 'ChargeRecorded',
data: {
chargeId,
guestStayAccountId,
amount,
recordedAt: now,
Expand All @@ -156,6 +164,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordPayment',
data: {
paymentId,
guestStayAccountId,
amount,
},
Expand All @@ -165,6 +174,7 @@ void describe('Guest Stay Account', () => {
{
type: 'PaymentRecorded',
data: {
paymentId,
guestStayAccountId,
amount,
recordedAt: now,
Expand Down Expand Up @@ -206,6 +216,7 @@ void describe('Guest Stay Account', () => {
{
type: 'ChargeRecorded',
data: {
chargeId,
amount,
guestStayAccountId,
recordedAt: oldTime,
Expand All @@ -218,6 +229,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordCharge',
data: {
chargeId: nextChargeId,
guestStayAccountId,
amount,
},
Expand All @@ -227,6 +239,7 @@ void describe('Guest Stay Account', () => {
{
type: 'ChargeRecorded',
data: {
chargeId: nextChargeId,
guestStayAccountId,
amount,
recordedAt: now,
Expand All @@ -240,6 +253,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordPayment',
data: {
paymentId,
guestStayAccountId,
amount,
},
Expand All @@ -249,6 +263,7 @@ void describe('Guest Stay Account', () => {
{
type: 'PaymentRecorded',
data: {
paymentId,
guestStayAccountId,
amount,
recordedAt: now,
Expand Down Expand Up @@ -292,6 +307,7 @@ void describe('Guest Stay Account', () => {
{
type: 'ChargeRecorded',
data: {
chargeId,
amount,
guestStayAccountId,
recordedAt: oldTime,
Expand All @@ -300,6 +316,7 @@ void describe('Guest Stay Account', () => {
{
type: 'PaymentRecorded',
data: {
paymentId,
amount,
guestStayAccountId,
recordedAt: oldTime,
Expand All @@ -312,6 +329,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordCharge',
data: {
chargeId: nextChargeId,
guestStayAccountId,
amount,
},
Expand All @@ -321,6 +339,7 @@ void describe('Guest Stay Account', () => {
{
type: 'ChargeRecorded',
data: {
chargeId: nextChargeId,
guestStayAccountId,
amount,
recordedAt: now,
Expand All @@ -334,6 +353,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordPayment',
data: {
paymentId: nextPaymentId,
guestStayAccountId,
amount,
},
Expand All @@ -343,6 +363,7 @@ void describe('Guest Stay Account', () => {
{
type: 'PaymentRecorded',
data: {
paymentId: nextPaymentId,
guestStayAccountId,
amount,
recordedAt: now,
Expand Down Expand Up @@ -386,6 +407,7 @@ void describe('Guest Stay Account', () => {
{
type: 'ChargeRecorded',
data: {
chargeId,
amount,
guestStayAccountId,
recordedAt: oldTime,
Expand All @@ -394,6 +416,7 @@ void describe('Guest Stay Account', () => {
{
type: 'PaymentRecorded',
data: {
paymentId,
amount,
guestStayAccountId,
recordedAt: oldTime,
Expand Down Expand Up @@ -428,6 +451,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordCharge',
data: {
chargeId: nextChargeId,
guestStayAccountId,
amount,
},
Expand All @@ -442,6 +466,7 @@ void describe('Guest Stay Account', () => {
.when({
type: 'RecordPayment',
data: {
paymentId: nextPaymentId,
guestStayAccountId,
amount,
},
Expand Down
2 changes: 2 additions & 0 deletions src/guestStayAccounts/guestStayAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type GuestCheckedIn = Event<
export type ChargeRecorded = Event<
'ChargeRecorded',
{
chargeId: string;
guestStayAccountId: string;
amount: number;
recordedAt: Date;
Expand All @@ -26,6 +27,7 @@ export type ChargeRecorded = Event<
export type PaymentRecorded = Event<
'PaymentRecorded',
{
paymentId: string;
guestStayAccountId: string;
amount: number;
recordedAt: Date;
Expand Down
85 changes: 85 additions & 0 deletions src/guestStayAccounts/guestStayDetails.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { GuestStayAccountEvent } from './guestStayAccount';

export type NotExisting = { status: 'NotExisting' };

export type Opened = {
id: string;
guestId: string;
roomId: string;
status: 'Opened' | 'CheckedOut';
balance: number;
transactionsCount: number;
transactions: { id: string; amount: number }[];
checkedInAt: Date;
checkedOutAt?: Date;
};

export type GuestStayDetails = NotExisting | Opened;

export const initialState = (): GuestStayDetails => ({
status: 'NotExisting',
});

export const evolve = (
state: GuestStayDetails,
{ type, data: event }: GuestStayAccountEvent,
): GuestStayDetails => {
switch (type) {
case 'GuestCheckedIn': {
return state.status === 'NotExisting'
? {
id: event.guestStayAccountId,
guestId: event.guestId,
roomId: event.roomId,
status: 'Opened',
balance: 0,
transactionsCount: 0,
transactions: [],
checkedInAt: event.checkedInAt,
}
: state;
}
case 'ChargeRecorded': {
return state.status === 'Opened'
? {
...state,
balance: state.balance - event.amount,
transactionsCount: state.transactionsCount + 1,
transactions: [
...state.transactions,
{ id: event.chargeId, amount: event.amount },
],
}
: state;
}
case 'PaymentRecorded': {
return state.status === 'Opened'
? {
...state,
balance: state.balance + event.amount,
transactionsCount: state.transactionsCount + 1,
transactions: [
...state.transactions,
{ id: event.paymentId, amount: event.amount },
],
}
: state;
}
case 'GuestCheckedOut': {
return state.status === 'Opened'
? {
...state,
status: 'CheckedOut',
checkedOutAt: event.checkedOutAt,
}
: state;
}
case 'GuestCheckoutFailed': {
return state;
}
default: {
const _notExistingEventType: never = type;
return state;
}
}
};
Loading

0 comments on commit f94ec1b

Please sign in to comment.