Living Systems Handbook microservices as humans Bipin Singh
Growing a human from DNA

Growing one human

2 min readChapter 14 of 34By Bipin Singh

Time to grow our first human: the Waiter of Café Sukoon. Its one job: take orders from customers and announce them. We'll write its DNA as a single CDK construct, then deploy it and talk to it.

The Waiter's body plan

Body part Implementation
Senses POST /orders and GET /orders/{orderId} on an HTTP API
Skin zod schema validation
Brain placeOrder business logic (the brain)
Memory Its own DynamoDB table, on-demand, point-in-time recovery
Voice Publishes OrderPlaced to the café's EventBridge bus
ID card An IAM role with access only to its table and the bus
Heart Structured logs, X-Ray tracing, an error alarm

The brain's helpers

The brain needs a menu to look up prices. For the pilot, a small fixed menu is enough:

// services/waiter/src/memory/menu.ts
import type { Menu } from "../brain/place-order";

const PRICES: Record<string, number> = { chai: 40, coffee: 120, samosa: 30, sandwich: 150 };

export const menu: Menu = {
  async priceOf(menuItemId) {
    return PRICES[menuItemId];
  },
};

The handler for reading an order:

// services/waiter/src/senses/get-order.http.ts
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from "aws-lambda";
import { orderMemory } from "../memory/order-memory";

export const handler = async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> => {
  const order = await orderMemory.recall(event.pathParameters?.orderId ?? "");
  return order
    ? { statusCode: 200, body: JSON.stringify(order) }
    : { statusCode: 404, body: JSON.stringify({ error: "Order not found" }) };
};

The place-order.http.ts handler, schema, brain, memory and voice modules are the ones built in the previous chapters.

The Waiter's DNA

// services/waiter/infra/waiter.ts
import * as path from "node:path";
import { Construct } from "constructs";
import { Duration, RemovalPolicy } from "aws-cdk-lib";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as events from "aws-cdk-lib/aws-events";
import * as apigw from "aws-cdk-lib/aws-apigatewayv2";
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";

export interface WaiterProps {
  /** The town square every human announces to */
  eventBus: events.IEventBus;
  /** The café's front door (shared HTTP API) */
  api: apigw.HttpApi;
}

export class Waiter extends Construct {
  public readonly memory: dynamodb.Table;

  constructor(scope: Construct, id: string, props: WaiterProps) {
    super(scope, id);

    // 🧠💾 Memory — owned by the Waiter alone
    this.memory = new dynamodb.Table(this, "Memory", {
      partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
      sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
      timeToLiveAttribute: "expiresAt",
      removalPolicy: RemovalPolicy.RETAIN, // never lose memories when the stack is deleted
    });
    this.memory.addGlobalSecondaryIndex({
      indexName: "gsi1",
      partitionKey: { name: "gsi1pk", type: dynamodb.AttributeType.STRING },
      sortKey: { name: "gsi1sk", type: dynamodb.AttributeType.STRING },
    });

    // Shared settings for the Waiter's brain cells (Lambda functions)
    const cell: Partial<nodejs.NodejsFunctionProps> = {
      runtime: lambda.Runtime.NODEJS_22_X,
      architecture: lambda.Architecture.ARM_64,
      memorySize: 512,
      timeout: Duration.seconds(10),
      tracing: lambda.Tracing.ACTIVE, // ❤️ follow each thought with X-Ray
      environment: {
        TABLE_NAME: this.memory.tableName,
        EVENT_BUS_NAME: props.eventBus.eventBusName,
        POWERTOOLS_SERVICE_NAME: "waiter",
      },
      bundling: { minify: true, sourceMap: true },
    };

    // 👂 Senses: place an order
    const placeOrder = new nodejs.NodejsFunction(this, "PlaceOrder", {
      ...cell,
      entry: path.join(__dirname, "../src/senses/place-order.http.ts"),
    });
    // 👂 Senses: recall an order
    const getOrder = new nodejs.NodejsFunction(this, "GetOrder", {
      ...cell,
      entry: path.join(__dirname, "../src/senses/get-order.http.ts"),
    });

    // 🪪 ID card: exactly the permissions each cell needs
    this.memory.grantReadWriteData(placeOrder);
    this.memory.grantReadData(getOrder);
    props.eventBus.grantPutEventsTo(placeOrder); // 🗣️ voice

    // 🚪 Connect the senses to the café's front door
    props.api.addRoutes({
      path: "/orders",
      methods: [apigw.HttpMethod.POST],
      integration: new HttpLambdaIntegration("PlaceOrderIntegration", placeOrder),
    });
    props.api.addRoutes({
      path: "/orders/{orderId}",
      methods: [apigw.HttpMethod.GET],
      integration: new HttpLambdaIntegration("GetOrderIntegration", getOrder),
    });

    // 💓 Pain receptor: alarm when orders fail
    placeOrder
      .metricErrors({ period: Duration.minutes(5) })
      .createAlarm(this, "PlaceOrderErrors", {
        threshold: 1,
        evaluationPeriods: 1,
        alarmDescription: "The Waiter is in pain: orders are failing",
      });
  }
}

The world the Waiter lives in

// infra/cafe-stack.ts — the society: shared world + humans
import { Stack, StackProps, CfnOutput } from "aws-cdk-lib";
import { Construct } from "constructs";
import * as events from "aws-cdk-lib/aws-events";
import * as apigw from "aws-cdk-lib/aws-apigatewayv2";
import { Waiter } from "../services/waiter/infra/waiter";

export class CafeStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const townSquare = new events.EventBus(this, "TownSquare");          // 📋 notice board
    const frontDoor = new apigw.HttpApi(this, "FrontDoor", {              // 🚪 for the outside world
      corsPreflight: { allowOrigins: ["*"], allowMethods: [apigw.CorsHttpMethod.ANY], allowHeaders: ["content-type"] },
    });

    new Waiter(this, "Waiter", { eventBus: townSquare, api: frontDoor }); // 🧍 our first human

    new CfnOutput(this, "FrontDoorUrl", { value: frontDoor.apiEndpoint });
  }
}
// bin/cafe.ts — the app
import { App } from "aws-cdk-lib";
import { CafeStack } from "../infra/cafe-stack";

const app = new App();
new CafeStack(app, "CafeSukoon-dev", {
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});
Watch out

allowOrigins: ["*"] and an unauthenticated API are fine for a first experiment in a sandbox. Before anyone real uses it, restrict CORS to your UI's domain and add authentication (Cognito + JWT authorizer) — see the immune system.

Birth

cdk synth      # check the DNA compiles into a template
cdk diff       # see what will be created
cdk deploy     # grow the Waiter

The output prints FrontDoorUrl. Talk to your new human:

URL=https://abc123.execute-api.ap-south-1.amazonaws.com   # from the output

curl -X POST "$URL/orders" \
  -H "content-type: application/json" \
  -d '{"tableNumber": 4, "items": [{"menuItemId": "chai", "quantity": 2}, {"menuItemId": "samosa", "quantity": 1}]}'
# → 201 {"orderId":"…","total":110,"status":"PLACED",…}

curl "$URL/orders/<orderId>"
# → 200 {…the remembered order…}

Then look inside the body:

Key idea

One construct, one human: memory, brain cells, senses, ID card and pain receptors grown together from a single piece of DNA. Every other human in the café will follow the same pattern.

Cleaning up

cdk destroy

Because the table has RemovalPolicy.RETAIN, it survives destruction — delete it manually in the console when you're done experimenting.

Bipin Singh
Written by Bipin Singh

Senior Full-Stack Engineer · AI & AWS. I design and build serverless systems on AWS — and love explaining them simply.

Work with me