1import * as AWS from "alchemy/AWS";
2import * as Effect from "effect/Effect";
3
4/**
5 * Shared infrastructure for the orders app.
6 *
7 * Each export is an Effect that declares a resource. Resources are memoized
8 * by logical id, so these can be yielded from the stack program AND from a
9 * service's init effect (e.g. `Api` binds the table and cluster) and always
10 * converge on the same single resource instance.
11 */
12
13/**
14 * A dedicated VPC with two public subnets (one per AZ — an internet-facing
15 * ALB requires two AZs). No NAT: services run in the public subnets with
16 * `assignPublicIp: true` so they can pull their images from ECR.
17 */
18export const OrdersNetwork = AWS.EC2.Network("OrdersNetwork", {
19 cidrBlock: "10.61.0.0/16",
20 availabilityZones: 2,
21});
22
23/** The ECS cluster every service and one-shot task in this app runs on. */
24export const OrdersCluster = AWS.ECS.Cluster("OrdersCluster", {});
25
26/** The orders table. Items are keyed `pk = order#<id>`. */
27export const OrdersTable = AWS.DynamoDB.Table("OrdersTable", {
28 partitionKey: "pk",
29 attributes: { pk: "S" },
30});
31
32/**
33 * The SHARED Application Load Balancer, owned at the stack level by neither
34 * service. `Api` and `Web` each attach their own listener rules + target
35 * groups to the single HTTP listener; requests no rule matches hit the
36 * listener's 404 default action. Destroying one service removes only its
37 * rules and target groups — the ALB and listener are untouched.
38 */
39export const OrdersIngress = Effect.gen(function* () {
40 const network = yield* OrdersNetwork;
41
42 const securityGroup = yield* AWS.EC2.SecurityGroup("AlbSecurityGroup", {
43 vpcId: network.vpcId,
44 description: "HTTP ingress for the shared orders ALB",
45 ingress: [
46 {
47 ipProtocol: "tcp",
48 fromPort: 80,
49 toPort: 80,
50 cidrIpv4: "0.0.0.0/0",
51 },
52 ],
53 });
54
55 const alb = yield* AWS.ELBv2.LoadBalancer("OrdersAlb", {
56 type: "application",
57 scheme: "internet-facing",
58 subnets: network.publicSubnetIds,
59 securityGroups: [securityGroup.groupId],
60 });
61
62 const listener = yield* AWS.ELBv2.Listener("OrdersHttpListener", {
63 loadBalancerArn: alb.loadBalancerArn,
64 port: 80,
65 protocol: "HTTP",
66 defaultActions: [
67 {
68 type: "fixedResponse",
69 statusCode: "404",
70 contentType: "text/plain",
71 messageBody: "no route",
72 },
73 ],
74 });
75
76 return { alb, listener };
77});