Skip to content

Instantly share code, notes, and snippets.

@sam-n-johnston
Last active December 13, 2023 02:09
Show Gist options
  • Save sam-n-johnston/bf783b5a3c35c0a969ae87f1209f57cc to your computer and use it in GitHub Desktop.
Save sam-n-johnston/bf783b5a3c35c0a969ae87f1209f57cc to your computer and use it in GitHub Desktop.
Improving Delivery with Subtasking
// express http endpoint
app.get('/orders', (req, res) => {
res.send([
{
id: 'smallTotalOrderId',
status: 'shipped',
datePlaced: '2000-10-31T01:30:00.000Z',
total: '0.99',
},
{
id: 'largeTotalOrderId',
status: 'shipped',
datePlaced: '2023-10-31T01:30:00.000Z',
total: '999999.99',
}
])
})
// real gateway implementation
export const getOrders = async (
customerId: string
): Promise<Order[]> => {
const response = await HTTPClient.get(
`orders?customer-id=${customerId}`
);
return response.json()
};
// dummy gateway implementation
export const getOrders = async (
customerId: string
): Promise<Order[]> => {
return [
{
id: 'smallTotalOrderId',
status: 'shipped',
datePlaced: '2000-10-31T01:30:00.000Z',
total: '0.99',
},
{
id: 'largeTotalOrderId',
status: 'shipped',
datePlaced: '2023-10-31T01:30:00.000Z',
total: '999999.99',
}
]
};
// dummy repo implementation
export class DummyOrderRespository implements OrderRepository {
public async getOrders(): Promise<Order[]> {
return [
{
id: 'smallTotalOrderId',
status: 'shipped',
datePlaced: '2000-10-31T01:30:00.000Z',
total: '0.99',
},
{
id: 'largeTotalOrderId',
status: 'shipped',
datePlaced: '2023-10-31T01:30:00.000Z',
total: '999999.99',
}
]
}
}

First PR(s)

The first PR(s) should create the interfaces that need to be implemented for the current ticket. In our example, it's the following:

// Both Website & Order Service
export type Order = {
    id: string;
    status: string;
    datePlaced: string;
    total: string;
}
// Order Service
export interface OrderRepository {
    getOrders(customerId: string): Promise<Order[]>;
}
// Order Service
export class GetOrdersQuery {
    constructor(
        public readonly customerId: string
    ) {}
}
// Website
export interface OrderServiceGateway {
    (customerId: string): Promise<Order[]>;
} 
// Website
export const orders = atom<Order[]>({
    key: 'orders',
    default: [],
});

PRs that can be done in parallel after First PRs

For brevity, unit tests are not included

Order Service - Repository

export class MySQLOrderRepository implements OrderRepository {
    async getOrders(customerId: string): Promise<Order[]> {
        const connection = mysql.createConnection({
            host: 'myHost',
            user: 'myUserName',
            password: 'myPassword'
        });

        const results = await new Promise((resolve, reject) => {
            connection.connect((err) => {
                connection.query(
                    `SELECT * FROM orders WHERE customerId=${req.query.id}`,
                    (err, result, fields) => {
                        resolve(result);
                    }
                );
            });
        });

        return results.map((order) => ({
            id: order.id,
            status: order.order_status,
            datePlaced: order.date_placed,
            total: order.display_total,
        }))
    }
}

Order Service - HTTP Endpoint

app.get('/orders', (req, res) => {
    // Requires the handler to be declared, but it can be empty. "container" is our DI container
    const getOrdersHandler = container.get(TYPES.GetOrdersHandler);
    const result = getOrdersHandler.handle(
        new GetOrdersQuery(req.query['customer-id'])
    );

    res.send(result)
})

Order Service - Core Application

export class GetOrdersHandler {
    constructor(private readonly repository: OrderRepository) {}

    async handle(query: GetOrdersQuery): Promise<Order[]> {
        // Perform any other business logic needed
        return this.repository.getOrders(query.customerId)
    }
}

Website - Order Service Gateway

export const getOrders: OrderServiceGateway = async (
    customerId: string
): Promise<Order[]> => {
    const response = await StockApiClient.get(
        `orders?customer-id=${customerId}`
    );
    return response.json()
};

Website - State Management

const createSelector = (serviceGateway: OrderServiceGateway) => {
    return selector<Order[]>({
        key: 'orders',
        get: async ({ get }) => {
            const customerId = get(currentCustomerId);

            return serviceGateway(customerId);
        },,
    });
}

// Use the getOrders instead of "() => Promise.resolve([])" when it's created
export const ordersSelector = createSelector(() => Promise.resolve([]))

Website - React

const MyApp: FunctionComponent = () => {
    // Set recoil state
    const [orders] = useRecoilState(orders);

    return (
        // Render order list
    );
};
export interface OrderRepository {
getOrders(customerId: string): Promise<Order[]>;
}
openapi: 3.0.3
info:
title: Order Sevice
description: Order Service Endpoints
version: '1.0'
paths:
/orders:
get:
summary: Get list of orders by customer Id
operationId: getOrders
responses:
'200':
description: Successful operation
content:
application/json:
schema:
type: array
items:
type: object
properties:
id:
type: string
example: '10b45'
status:
type: string
example: 'shipped'
datePlaced:
type: string
example: '2017-07-21T17:32:28Z'
total:
type: string
example: '575.60'
'404':
description: Order not found
parameters:
- in: query
name: customer-id
schema:
type: string
app.get("/orders", async (req, res) => {
const queryValues = await new Promise((resolve, reject) => {
const connection = mysql.createConnection({
// ...
});
connection.connect((err) => {
connection.query(
`SELECT * FROM orders WHERE customerId=${req.query.id}`,
(err, result, fields) => {
resolve(result);
}
);
});
});
const orders = queryValues.map((queryValue) => ({
id: queryValue.orderId,
status: queryValue.status,
datePlaced: queryValue.date_placed,
total: queryValue.display_total,
}));
res.send(orders);
});
export type Order = {
id: string;
status: string;
datePlaced: string;
total: string;
}
export const orderListState = atom<Order[]>({
key: 'orders',
default: [],
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment