Solution Architecture · Lesson 2 of 6
Gathering and Analyzing Non-Functional Requirements
Requirements Drive Architecture
A system's architecture is not chosen — it is derived from its requirements.
Functional requirements: what the system does
→ "Clinicians can create and review prescriptions"
→ "The system must check INR values before approving Warfarin doses"
→ These tell you what modules, entities, and APIs you need
Quality attributes (NFRs): how the system behaves under conditions
→ "99.9% uptime during ward hours (06:00–22:00)"
→ "Prescription lookup must respond in under 500ms at 500 concurrent users"
→ "All patient data access must be logged for MHRA audit"
→ These tell you which architectural patterns to apply
Constraints: things you cannot change
→ "Must integrate with existing HL7 FHIR R4 patient registry"
→ "Must run on Azure — no AWS or GCP"
→ "Team has 3 .NET developers, no microservices operational experience"
→ These eliminate options before you start designing
Get all three wrong, and the architecture fits nobody.
Get NFRs wrong specifically, and you build the right thing that collapses in production.Quality Attribute Workshop
Run a quality attribute workshop before writing architecture:
1. List candidate quality attributes (availability, performance, security, etc.)
2. Stakeholders prioritize: which matter most for THIS system?
3. For each high-priority attribute, define measurable scenarios
Quality Attribute Scenario format:
Stimulus: what triggers the concern
Source: who or what triggers it
Environment: what state is the system in
Artifact: what part of the system is affected
Response: what the system does
Measure: how you verify it
Example:
Quality Attribute: Performance
Stimulus: Nurse submits a prescription search
Source: Authenticated clinical user
Environment: Normal operation, 200 concurrent users, ward shift change
Artifact: Prescriptions query service
Response: Returns matching results
Measure: p95 response time under 400ms, p99 under 800ms, 0 timeoutsFrom NFRs to Architecture Decisions
// NFR: "All Warfarin prescriptions require an INR check within 24 hours before approval"
// → Architectural implication: workflow state machine, not a simple CRUD endpoint
// Without this NFR, you might write:
app.MapPost("/prescriptions", async (CreatePrescriptionCommand cmd, ISender sender) =>
await sender.Send(cmd));
// A simple command with no state tracking
// With this NFR, you need:
// 1. A Prescription aggregate with a Status property (Draft → PendingInrCheck → Approved/Rejected)
// 2. A domain service that checks INR results
// 3. A background job that expires pending prescriptions after 24 hours
// 4. Audit logging on every status transition
public sealed class Prescription : Entity<PrescriptionId>
{
public PrescriptionStatus Status { get; private set; }
public DateTime? InrCheckedAt { get; private set; }
public decimal? InrValueAtApproval { get; private set; }
public Result ApproveWithInrCheck(decimal inrValue, DateTime checkedAt)
{
if (Status != PrescriptionStatus.PendingInrCheck)
return Result.Failure(Error.Validation("Status",
"Prescription is not awaiting INR check."));
if (checkedAt < DateTime.UtcNow.AddHours(-24))
return Result.Failure(Error.Validation("InrCheck",
"INR check is older than 24 hours."));
Status = PrescriptionStatus.Approved;
InrCheckedAt = checkedAt;
InrValueAtApproval = inrValue;
RaiseDomainEvent(new PrescriptionApprovedDomainEvent(Id, inrValue, checkedAt));
return Result.Success();
}
}
// This state machine exists entirely because of one NFR.Constraint Analysis
Constraint type: Technical
"Must use SQL Server — already licensed"
→ Rules out PostgreSQL, MongoDB, Cosmos DB
→ EF Core with SQL Server or Dapper, not document mappers
Constraint type: Organizational
"Two-week sprints, 4-person team, no dedicated DBA"
→ Rules out complex database sharding strategies
→ Managed Azure SQL preferred over self-hosted SQL Server cluster
Constraint type: Regulatory
"MHRA-regulated medical device — audit trail required"
→ Every data change must be logged with user, timestamp, before/after values
→ Soft deletes only — no hard deletes of clinical data
→ Event sourcing or an audit table pattern required
Constraint type: Integration
"Existing HL7 FHIR R4 patient registry — cannot migrate patient data"
→ PatientService becomes an Anti-Corruption Layer over FHIR
→ All patient reads go through FHIR translation, not a local patient DB
→ Eventual consistency between FHIR data and local snapshots
Undocumented constraints are the most dangerous.
"The team can't operate Kubernetes" is a constraint.
"The hospital network blocks outbound HTTPS on port 443" is a constraint.
Find them in week 1, not week 12.Requirements to Architecture Matrix
Requirement → Architecture Decision
─────────────────────────────────────────────────────────────────────────
99.9% uptime during ward hours → Active/passive failover, Azure SQL geo-replication
Sub-500ms p95 on search → Denormalized read model, indexes, no N+1 queries
MHRA audit trail → Audit interceptor or event sourcing
FHIR R4 integration → Anti-Corruption Layer (FhirPatientAdapter)
Team: 3 .NET devs, no K8s exp → Modular monolith on Azure App Service, not microservices
Data: never delete clinical records → Soft deletes (IsDeleted flag), EF Core query filter
Scale: 500 concurrent users → Stateless app tier, connection pooling, Redis cache
Multi-ward, multi-hospital → Tenant-aware queries, row-level security or schema isolationProduction issue I've seen: A clinical system was designed for a single hospital. The NFR said "support 50 concurrent users." It used in-memory caching with no eviction strategy and a single SQL Server with no read replicas. Two years post-launch, the system was licensed to 12 hospitals with 800 concurrent users. Response times went from 200ms to 14 seconds. The architecture had to be rewritten from scratch — not because anyone made a technical mistake, but because nobody captured "the system may expand to multiple sites" as a quality attribute or constraint. The original architect documented a single sentence in an email, not in a requirements document.
Key Takeaway
Requirements come in three types: functional (what), quality attributes (how well), and constraints (what's fixed). Quality attributes are the most critical architectural input — each one drives at least one structural decision. Run a quality attribute workshop before choosing patterns or technologies. Document constraints explicitly: team skills and operational capability are constraints as real as budget or regulation. Every significant architecture decision should trace back to a requirement or constraint.