Tech Strategy
How to write a technology strategy that actually constrains decisions: diagnosis, guiding policy and coherent action, build-versus-buy, consolidation, platform bets, and why most strategy documents are roadmaps in disguise.
Tech strategy is the deliberate, ongoing process of aligning technical capabilities with organisational goals to build sustainable competitive advantage. It matters when teams are no longer just shipping features but are deciding *what* to build, *why*, and *how* across systems, teams, and time.
Deciding What to Build
A team selects what to build not from a backlog, but from a strategic lens: what will move the needle on long-term objectives? The answer comes not from stakeholder interviews alone, but from a structured evaluation of technical and business trade-offs.
The core tool is the Strategic Roadmap Canvas, a document that evolves quarterly. It is maintained as a roadmap.yaml file in the project root:
version: 1
strategic_goals:
- name: "Reduce customer onboarding time"
metric: "Time from signup to first successful transaction"
target: 48h
timeline: 18mo
- name: "Enable multi-region deployment"
metric: "99.95% availability across regions"
target: 12mo
initiatives:
- id: "cloud-native-core"
title: "Build a cloud-native service layer"
priority: 1
owner: "platform-engineering"
technologies:
- "Kubernetes"
- "gRPC"
- "OpenTelemetry"
dependencies:
- "cloud-native-core"
- "event-sourcing"
risks:
- "Increased latency for edge users (P99 > 500ms)"
- "SRE team must adopt new observability stack"
Each initiative is evaluated against a Strategic Fit Matrix, a matrix of two axes: *Strategic Impact* (high, medium, low) and *Technical Feasibility* (high, medium, low). Initiatives in the high-high quadrant are prioritised for immediate investment.
The team runs a quarterly Tech Strategy Workshop, using git-based collaboration:
git checkout -b workshop/q2-2024
# Add a new strategic goal
echo ' - name: "Improve API response consistency"
metric: "P95 API latency < 150ms across all endpoints"
target: 6mo' >> roadmap.yaml
# Propose a new initiative
cat > initiatives/cloud-native-core.md << 'EOF'
### Initiative: Cloud-Native Core
- **Objective**: Replace monolithic service with modular, independently deployable services.
- **Success Metrics**:
- 90% of services deployed daily via CI/CD.
- 50% reduction in mean time to recovery (MTTR).
- **Technical Requirements**:
- All services must use JSON:API for request/response.
- All services must emit OpenTelemetry traces with standardized attributes.
EOF
git add .
git commit -m "Add cloud-native-core initiative and update roadmap"
git push origin workshop/q2-2024
A pull request is opened. Reviewers evaluate against the canvas, asking: *Does this initiative align with the strategic goal? Does it reduce technical debt in a measurable way? What risks are under-estimated?*
Deciding Why to Build It
A decision to build something is not complete until the "why" is explicit and shared across teams. The Why Statement Template is used to articulate the rationale behind every initiative.
It is a why.md file in the initiative’s directory:
## Why: Cloud-Native Core
### Problem
- Monolith grows slowly, takes 45 minutes to deploy.
- On-call SREs spend 40% of time debugging inter-service issues.
- No consistent error reporting: logs, metrics, and traces are disconnected.
### Goal
- Enable teams to own and release services independently.
- Reduce deployment downtime and increase reliability.
### Rationale
- A cloud-native architecture reduces latency and improves fault isolation.
- Gains in developer velocity outweigh the cost of new tooling.
- Enables eventual migration to serverless and AI-driven operations.
### Success Indicators
- 80% of teams adopt the service template within 3 months.
- 30% reduction in on-call incident volume after 6 months.
- 95% of services meet SLI targets (P95 latency < 100ms).
The team validates the "why" by running a Strategic Alignment Review, a 90-minute session where stakeholders review the initiative against the roadmap. The outcome is a Strategic Fit Scorecard, a table of metrics and thresholds:
| Metric | Current | Target | Threshold | Status |
|---|---|---|---|---|
| Mean deployment time (min) | 45 | 15 | ≤ 20 | ✗ |
| P95 API latency (ms) | 210 | 100 | ≤ 120 | ✗ |
| SRE incident volume (per week) | 22 | 15 | ≤ 18 | ✓ |
The review reveals a subtle but critical failure mode: the team assumed that "independent deployment" meant "CI/CD pipelines per service," but the deployment tooling was not standardized. They discover that 70% of services use different CI systems (GitHub Actions, GitLab CI, Jenkins), leading to inconsistent release practices.
Deciding How to Build It
Once the "what" and "why" are clear, the team must design the "how" — the technical approach that enables the strategy. The design is captured in a System Design Document using a standard template.
The document is stored in designs/cloud-native-core.md:
# System Design: Cloud-Native Core
## Overview
A service-oriented architecture with bounded contexts, event-driven communication, and shared observability.
## Architecture
- **Deployment**: Kubernetes clusters in AWS us-east-1 and eu-west-1.
- **Service Communication**: gRPC over HTTP/2, with retry, timeout, and circuit-breaking.
- **Data Storage**:
- Event logs: Apache Kafka (topic: `events`).
- State: PostgreSQL (schema versioned with Liquibase).
- **Observability**:
- Tracing: OpenTelemetry with trace context propagation via HTTP headers.
- Metrics: Prometheus with service-level SLIs.
- Logs: Structured JSON, exported to Elasticsearch.
## API Contract
All services expose a `GET /health` endpoint:
```json
{
"status": "healthy",
"version": "v1.2.0",
"uptime": 123456,
"dependencies": {
"database": {
"status": "up",
"latency_ms": 12
},
"kafka": {
"status": "up",
"lag": 42
}
}
}
Error Responses
400 Bad Request:{ "error": "invalid_input", "details": ["field.email.required"] }503 Service Unavailable:{ "error": "timeout", "retry_after": 30 }429 Too Many Requests:{ "error": "rate_limit_exceeded", "limit": 1000, "remaining": 873 }
Deployment Strategy
- Canary Rollout: 10% traffic to new version; if P99 latency < 200ms for 5 minutes, promote to 50%.
- Rollback: If health check fails for 3 consecutive minutes, revert to previous version.
Configuration
Configurations are managed via config.yaml and loaded at startup:
app:
environment: "production"
log_level: "info"
service:
name: "order-service"
version: "v1.2.0"
tracing:
enabled: true
exporter: "otlp"
endpoint: "http://otlp-collector:4317"
metrics:
prometheus:
enabled: true
endpoint: "/metrics"
slis:
- name: "api_latency_p95"
target: 100
threshold: 150
window: "5m"
Failure Modes
- Tracing Context Loss: If
traceparentheader is missing, traces are fragmented. - Kafka Consumer Lag: If lag exceeds 10,000 messages, alert via Slack.
- Service Health Check Failure: After 3 consecutive failures, service is marked unhealthy and traffic is routed to backup.
Monitoring and Alerting
Alerts are defined in alerting.yaml:
- name: "Service Unavailable"
query: "up{job=\"order-service\"} == 0"
for: "5m"
severity: "critical"
labels:
component: "order-service"
service: "orders"
annotations:
summary: "Order service is down"
description: "The order-service has been down for {{ .Values.for }}."
- name: "High Latency"
query: "rate(http_request_duration_seconds_bucket{job=\"order-service\"}[5m]) > 0.5"
for: "2m"
severity: "warning"
labels:
service: "orders"
The team uses curl to simulate a failure during a design review:
curl -X POST http://order-service:8080/orders \
-H "Content-Type: application/json" \
-d '{"customer_id": 123, "items": [{"id": 456, "quantity": 2}]}'
The response includes a traceparent header:
HTTP/1.1 201 Created
Content-Type: application/json
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
This header is used by downstream services to stitch traces across microservices.
Evaluating and Refining the Strategy
The strategy is not static. Every quarter, the team evaluates its effectiveness using a Strategy Review Dashboard, a dashboard.yaml file:
version: 1
review_cycle: "Q2 2024"
objectives:
- "Reduce customer onboarding time"
- "Enable multi-region deployment"
metrics:
- name: "Onboarding time (hours)"
current: 72
target: 48
trend: "down"
data: [75, 73, 71, 69, 68, 67, 66, 64, 63, 62, 60, 58]
- name: "P99 API latency (ms)"
current: 210
target: 100
trend: "up"
data: [220, 215, 212, 210, 208, 205, 200, 195, 190, 185, 180, 175]
- name: "Incident volume (per week)"
current: 22
target: 15
trend: "down"
data: [24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13]
The team runs strategy-review.sh to generate a report:
#!/bin/bash
# strategy-review.sh
set -euo pipefail
echo "## Tech Strategy Review: Q2 2024"
echo
# Render metrics
for metric in $(yq '.metrics | keys | .[]' dashboard.yaml); do
current=$(yq ".metrics[] | select(.name == \"$metric\") | .current" dashboard.yaml)
target=$(yq ".metrics[] | select(.name == \"$metric\") | .target" dashboard.yaml)
trend=$(yq ".metrics[] | select(.name == \"$metric\") | .trend" dashboard.yaml)
echo "- $metric: $current (target: $target) [trend: $trend]"
done
echo
# Identify gaps
echo "### Gaps Identified"
yq '.metrics[] | select(.current < .target) | .name' dashboard.yaml | while read g; do
echo "- $g: current is below target"
done
The review reveals that while onboarding time is improving, P99 API latency is trending upward. The team discovers that a recent change in the event-sourcing layer introduced a 150ms delay in processing events — a silent failure that was not part of the original design.
The Unseen Failures
The strategy fails silently when teams adopt tools without understanding their implications.
- Misunderstanding of tracing context: Services propagate
traceparentbut fail to extract it from incoming requests. As a result, traces are fragmented, and debugging cross-service issues takes twice as long. - Inconsistent error formats: Some services return
{"error": "bad_request"}, others{ "message": "error" }. A gateway service must now handle both, adding complexity. - Kafka consumer lag: The team assumes "lag" means "messages behind", but they fail to define what "behind" means — the lag is measured from the producer’s last message, not from the consumer’s current offset. This leads to confusion during scaling events.
- Service health checks: A service returns
200 OKbut fails thehealthendpoint. The health check passes, but the service is actually unhealthy due to a database connection pool exhaustion.
These failures are not caught in testing but emerge only in production. The team learns that the "Health Check Protocol" must be standardized: every service must include a GET /health endpoint that returns a 200 status and a json body with status, uptime, and dependencies, and that dependencies must be a map of service names to objects with status and latency_ms.
The strategy is only as strong as the shared understanding of these patterns. And every time a team deploys a new service, they are not just shipping code — they are reinforcing the tech strategy.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.