Skip to content

Commit e9b4c3e

Browse files
committed
feat: refactor CircleCI config to implement fan-out/fan-in pattern for improved efficiency
1 parent 0ff9b2a commit e9b4c3e

1 file changed

Lines changed: 190 additions & 30 deletions

File tree

.circleci/config.yml

Lines changed: 190 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,86 @@
11
# ============================================================================
2-
# MODULE 1: ANTI-PATTERN - Sequential Pipeline (The "Before" State)
2+
# MODULE 1: Workflow Orchestration Best Practices
33
# ============================================================================
4-
# This config demonstrates what NOT to do. Every job waits for the previous
5-
# one, even when there's no real dependency. We reinstall deps in every job.
4+
# Fan-out/Fan-in pattern: Build once, test in parallel, converge to deploy.
5+
# Uses workspace to share node_modules across jobs (install once, use many).
66
#
7-
# PAIN POINTS TO OBSERVE IN CIRCLECI UI:
8-
# - Total time = sum of all jobs (no parallelism)
9-
# - Each job reinstalls node_modules (~30-60 sec wasted per job)
10-
# - Pipeline visualization shows a straight line, not a diamond
7+
# WORKFLOW VISUALIZATION:
8+
#
9+
# require-approval=true (default):
10+
# ┌─► lint ──────────┐
11+
# │ │
12+
# build ──────────►├─► test-unit ─────┼──► [approve-deploy] ──► deploy-simulation
13+
# │ │
14+
# ├─► test-integ ────┤
15+
# │ │
16+
# └─► test-e2e ──────┘
17+
#
18+
# require-approval=false (auto-deploy):
19+
# ┌─► lint ──────────┐
20+
# │ │
21+
# build ──────────►├─► test-unit ─────┼──────► deploy-simulation
22+
# │ │
23+
# ├─► test-integ ────┤
24+
# │ │
25+
# └─► test-e2e ──────┘
26+
#
27+
# Total time: ~2-3 min (parallel) vs ~6-8 min (sequential)
1128
# ============================================================================
1229

1330
version: 2.1
1431

32+
# ============================================================================
33+
# PIPELINE PARAMETERS: Feature flags configurable via API, UI, or triggers
34+
# ============================================================================
35+
parameters:
36+
require-approval:
37+
type: boolean
38+
default: true
39+
description: "Require manual approval before deploy (false = auto-deploy)"
40+
1541
jobs:
42+
# --------------------------------------------------------------------------
43+
# BUILD: Install deps once, persist to workspace for all downstream jobs
44+
# --------------------------------------------------------------------------
1645
build:
1746
docker:
1847
- image: cimg/node:20.18
1948
steps:
2049
- checkout
50+
# CACHING: Restores node_modules from cache if package-lock.json unchanged
51+
# First key = exact match, second key = partial fallback (may need npm ci)
52+
- restore_cache:
53+
keys:
54+
- deps-v1-{{ checksum "package-lock.json" }}
55+
- deps-v1-
2156
- run:
2257
name: Install dependencies
2358
command: npm ci
59+
# Save to cache after install; only runs if key doesn't already exist
60+
- save_cache:
61+
key: deps-v1-{{ checksum "package-lock.json" }}
62+
paths:
63+
- node_modules
2464
- run:
2565
name: Build application
2666
command: npm run build
67+
# KEY CONCEPT: Persist workspace so downstream jobs skip npm ci
68+
- persist_to_workspace:
69+
root: .
70+
paths:
71+
- node_modules
72+
- dist
2773

74+
# --------------------------------------------------------------------------
75+
# PARALLEL JOBS: All attach workspace, run simultaneously after build
76+
# --------------------------------------------------------------------------
2877
lint:
2978
docker:
3079
- image: cimg/node:20.18
3180
steps:
3281
- checkout
33-
- run:
34-
name: Install dependencies (again!)
35-
command: npm ci # WASTE: Already installed in build
82+
- attach_workspace:
83+
at: .
3684
- run:
3785
name: Run linter
3886
command: npm run lint
@@ -42,42 +90,83 @@ jobs:
4290
- image: cimg/node:20.18
4391
steps:
4492
- checkout
45-
- run:
46-
name: Install dependencies (again!)
47-
command: npm ci # WASTE: Third time installing
93+
- attach_workspace:
94+
at: .
4895
- run:
4996
name: Run unit tests
5097
command: npm run test:unit
98+
# store_test_results: Uploads JUnit XML for CircleCI's Test Insights tab
99+
# Enables test timing analysis, flaky test detection, and historical trends
100+
- store_test_results:
101+
path: test-results
102+
- store_artifacts:
103+
path: coverage
104+
destination: unit-coverage
51105

52106
test-integration:
53107
docker:
54108
- image: cimg/node:20.18
55109
steps:
56110
- checkout
57-
- run:
58-
name: Install dependencies (again!)
59-
command: npm ci # WASTE: Fourth time
111+
- attach_workspace:
112+
at: .
60113
- run:
61114
name: Run integration tests
62115
command: npm run test:integration
116+
# Parses test XML for the Insights dashboard (separate from artifacts)
117+
- store_test_results:
118+
path: test-results
63119

64120
test-e2e:
65121
docker:
66122
- image: cimg/node:20.18
67123
steps:
68124
- checkout
69-
- run:
70-
name: Install dependencies (again!)
71-
command: npm ci # WASTE: Fifth time!
125+
- attach_workspace:
126+
at: .
72127
- run:
73128
name: Run E2E tests
74129
command: npm run test:e2e
130+
# Test results enable automatic test splitting in future parallelism configs
131+
- store_test_results:
132+
path: test-results
133+
134+
# --------------------------------------------------------------------------
135+
# DEPLOY SIMULATION: Fan-in - requires ALL parallel jobs to pass
136+
# --------------------------------------------------------------------------
137+
deploy-simulation:
138+
docker:
139+
- image: cimg/node:20.18
140+
steps:
141+
- checkout
142+
- attach_workspace:
143+
at: .
144+
- run:
145+
name: Deploy Robot Fleet API (simulated)
146+
command: |
147+
echo "==========================================="
148+
echo " DEPLOYING GLOBOMANTICS ROBOT FLEET API"
149+
echo "==========================================="
150+
# CIRCLE_BRANCH: Git branch that triggered this pipeline
151+
echo " Branch: ${CIRCLE_BRANCH}"
152+
# CIRCLE_SHA1: Full 40-char Git commit SHA (truncated here to 7)
153+
echo " Commit: ${CIRCLE_SHA1:0:7}"
154+
# CIRCLE_BUILD_NUM: Auto-incrementing build number for this project
155+
echo " Build: ${CIRCLE_BUILD_NUM}"
156+
echo "==========================================="
157+
echo "Deployment successful! (simulated)"
75158
76159
# ============================================================================
77-
# WORKFLOW: Everything runs sequentially - SLOW!
160+
# WORKFLOW: Fan-out after build, fan-in before deploy
78161
# ============================================================================
162+
# Two workflows controlled by pipeline parameter - only one runs at a time
79163
workflows:
80-
m1-sequential-anti-pattern:
164+
# --------------------------------------------------------------------------
165+
# WORKFLOW 1: With approval gate (default when require-approval=true)
166+
# --------------------------------------------------------------------------
167+
m1-build-test-deploy:
168+
# WHEN at workflow level: entire workflow only runs if condition is true
169+
when: << pipeline.parameters.require-approval >>
81170
jobs:
82171
- build:
83172
filters:
@@ -86,31 +175,102 @@ workflows:
86175
- main
87176
- /feature\/.*/
88177

89-
# ANTI-PATTERN: lint waits for build, but doesn't need build output
178+
# FAN-OUT: These 4 jobs all require build, so they run in PARALLEL
90179
- lint:
91180
requires:
92181
- build
93182

94-
# ANTI-PATTERN: These tests don't depend on each other, yet run sequentially
95183
- test-unit:
96184
requires:
97-
- lint
185+
- build
98186

99187
- test-integration:
100188
requires:
189+
- build
190+
191+
- test-e2e:
192+
requires:
193+
- build
194+
195+
# APPROVAL GATE: Pauses workflow until someone clicks "Approve" in UI
196+
# type: approval = special job with no compute cost while waiting
197+
- approve-deploy:
198+
type: approval
199+
requires:
200+
- lint
101201
- test-unit
202+
- test-integration
203+
- test-e2e
204+
filters:
205+
branches:
206+
only: main
207+
208+
# FAN-IN: Deploy only after manual approval
209+
- deploy-simulation:
210+
requires:
211+
- approve-deploy
212+
filters:
213+
branches:
214+
only: main
215+
216+
# --------------------------------------------------------------------------
217+
# WORKFLOW 2: Auto-deploy without approval (when require-approval=false)
218+
# --------------------------------------------------------------------------
219+
m1-build-test-deploy-auto:
220+
# UNLESS at workflow level: runs when condition is false (inverse of when)
221+
unless: << pipeline.parameters.require-approval >>
222+
jobs:
223+
- build:
224+
filters:
225+
branches:
226+
only:
227+
- main
228+
- /feature\/.*/
229+
230+
- lint:
231+
requires:
232+
- build
233+
234+
- test-unit:
235+
requires:
236+
- build
237+
238+
- test-integration:
239+
requires:
240+
- build
102241

103242
- test-e2e:
104243
requires:
244+
- build
245+
246+
# FAN-IN: Deploy directly after tests pass (no approval gate)
247+
- deploy-simulation:
248+
requires:
249+
- lint
250+
- test-unit
105251
- test-integration
252+
- test-e2e
253+
filters:
254+
branches:
255+
only: main
106256

107257
# ============================================================================
108-
# WHAT'S WRONG HERE (Module 1 Learning Points):
258+
# MODULE 1 KEY CONCEPTS DEMONSTRATED:
259+
#
260+
# 1. REQUIRES KEYWORD: Creates job dependencies (build → tests → deploy)
261+
# 2. FAN-OUT: Multiple jobs with same requirement run in parallel
262+
# 3. FAN-IN: Single job requiring multiple predecessors waits for all
263+
# 4. BRANCH FILTERS: Control which branches trigger which jobs
264+
# 5. WORKSPACE: Share artifacts between jobs (eliminates redundant npm ci)
265+
# 6. APPROVAL GATES: type: approval pauses workflow for manual approval
266+
# 7. PIPELINE PARAMETERS: Feature flags to control workflow behavior
267+
# 8. CONDITIONAL WORKFLOWS: when/unless at WORKFLOW level toggles entire flows
268+
# (Note: when/unless must be at workflow level, not individual job level)
109269
#
110-
# 1. FALSE DEPENDENCIES: lint doesn't need build output - just source code
111-
# 2. NO PARALLELISM: All test jobs could run at the same time
112-
# 3. NO WORKSPACE: npm ci runs 5 times instead of once
113-
# 4. TOTAL TIME: ~6-8 min sequential vs ~2-3 min with fan-out
270+
# TRIGGERING WITH PARAMETERS (via API or curl):
271+
# curl -X POST https://circleci.com/api/v2/project/gh/OWNER/REPO/pipeline \
272+
# -H "Circle-Token: $TOKEN" -H "Content-Type: application/json" \
273+
# -d '{"parameters": {"require-approval": false}}'
114274
#
115-
# Next config (02) shows the fix using fan-out/fan-in patterns.
275+
# CREDIT CONSUMPTION: Same as sequential! You pay for compute time, not wall time.
116276
# ============================================================================

0 commit comments

Comments
 (0)