Software Engineering: Process, Requirements, Design and Quality


Comprehensive software engineering notes on lifecycle, project management, quality, maintenance, requirements, design, architecture, and reproducible AI/ML configuration.
Process models, project management, quality, testing, maintenance, requirements, and design are treated as connected engineering concerns rather than isolated definitions. Classical methods remain important in their historical context, while DevOps, continuous delivery, and current quality and life-cycle standards represent later practice and are identified accordingly.
Unit 1: Introduction to Software Engineering
1.1. Software and Software Engineering
A program is executable logic; a software product also includes data, configuration, interfaces, documentation, deployment artifacts and operational assumptions. Software engineering applies systematic engineering methods to the specification, design, construction, verification, operation and evolution of such systems.
The purpose is not bureaucracy. It is to control complexity, change and risk when software is larger or longer-lived than one developer can safely hold in working memory.
1.2. Program, Software and Hardware
Hardware fails through physical mechanisms and aging. Software does not wear out in the same physical sense, but it degrades operationally when changes accumulate, dependencies evolve and assumptions no longer match the environment.
Software maintenance is therefore primarily change management, defect correction, adaptation and controlled evolution.
1.3. Software Engineer and Production Environment
Writing correct code is only part of engineering work. Production software also requires:
- version and configuration control,
- build/release repeatability,
- testing,
- deployment and rollback planning,
- observability,
- security controls,
- capacity and failure analysis,
- documentation of interfaces and assumptions.
1.4. Classification of Software Systems
Software can be embedded, real-time, interactive, batch, scientific, information-system, safety/security critical, mobile, distributed or infrastructure software. These categories overlap. The engineering method must follow the dominant risks of the system rather than one universal process template.
1.5. Software Crisis and Continuing Complexity
The historical "software crisis" described projects that exceeded cost/schedule, delivered unreliable systems or became impossible to maintain as complexity grew.
Modern tools improved languages, automation and infrastructure, but the underlying problem remains: requirements change, interfaces multiply, systems become distributed and human coordination does not scale automatically with code size.
1.6. Main Sources of Software Problems
Common causes include:
- incomplete or changing requirements,
- weak architecture,
- uncontrolled dependencies,
- underestimated integration effort,
- poor testability,
- unowned technical debt,
- unrealistic schedules,
- weak configuration/release discipline,
- insufficient feedback from production.
1.7. Error Propagation Through the Life Cycle
A misunderstood requirement can become a design decision, then code, then test assumptions and eventually operational failure. Detecting a defect earlier usually reduces the cost of correction because fewer dependent artifacts must change.
This does not mean every requirement can be frozen early; it means feedback cycles should expose errors before they propagate too far.
1.8. Software Development Life Cycle
A generic lifecycle contains:
need / concept
↓
requirements
↓
design
↓
implementation
↓
verification and validation
↓
deployment / transition
↓
operation and maintenance
↓
retirementThe phases can overlap and iterate. ISO/IEC/IEEE 12207:2026 provides a current common framework for software life-cycle processes, while earlier lifecycle formulations remain useful in their historical context.
1.9. Development Plan
A project plan defines scope, deliverables, responsibilities, milestones, resources, risks, quality activities and change/control mechanisms. A plan should be updated from evidence; it is not useful if maintained only as a document detached from actual project state.
1.10. Requirements Analysis
Requirements describe externally meaningful behavior, constraints and quality properties. They should be traceable to stakeholders and verifiable enough that completion can be evaluated.
1.11. Design
Design converts requirements into architecture, components, data structures, interfaces and responsibilities. Good design controls coupling and makes expected change local rather than spreading through the system.
1.12. Testing
Testing executes or evaluates the system to find failures and gather evidence about conformance. Tests cannot prove the absence of every defect in a non-trivial program; they sample behaviors according to a designed strategy.
1.13. Maintenance and Evolution
Maintenance includes corrective, adaptive, perfective and preventive change. A system with poor modularity or test coverage may spend most of its lifecycle cost after initial delivery.
1.14. Process Maturity
Process maturity means the organization can perform and improve engineering activities predictably. It should not be confused with the number of forms or approvals. A repeatable automated process can be more mature than a document-heavy process that is not followed.
1.15. Software Process Models
Different process models organize uncertainty and feedback differently.
1.16. Ad Hoc and Historical Linear Models
An ad hoc process organizes development around immediate actions rather than an explicit, verified sequence of requirements, design, implementation, and acceptance. It may produce a small exploratory result quickly, but it makes dependencies, ownership, and regression evidence difficult to control. Historical linear models offer more predictable gates at the cost of delayed feedback. Neither label by itself establishes product quality; the relevant variables are uncertainty, change cost, and verification latency.
1.17. Waterfall Model
A strict waterfall treats stages as mostly sequential. It is easy to explain and can fit domains with stable requirements and formal gates, but it performs poorly when important uncertainty is discovered late.
1.18. V Model
The V-model pairs specification/design levels with corresponding verification levels. It emphasizes planning tests against requirements and architecture rather than waiting until implementation is complete.
1.19. Prototype-Supported V Approach
A prototype-supported V approach supplements the V-model with targeted experiments before expensive commitments. Interface prototypes can test user tasks, technical prototypes can measure latency or resource limits, and integration prototypes can expose incompatible contracts. Each prototype must have a stated question, acceptance criterion, and disposal or hardening decision. Verification remains linked to the corresponding specification and design level.
1.20. Prototyping
A prototype reduces uncertainty about interfaces, usability or technical feasibility. A throwaway prototype should not silently become production architecture without re-evaluating quality requirements.
1.21. From Fourth-generation Techniques to Code Generation
Historical fourth-generation tools aimed to generate large parts of applications from higher-level models or declarative descriptions. Modern low-code tools, ORM generators, schema compilers and model-based toolchains continue the same idea in different forms.
1.22. Evolutionary and Research-driven Development
When requirements are uncertain, iterative experiments can reduce technical risk. The important control is to distinguish exploratory code from components that have been hardened for production constraints.
1.23. Rapid Application Development (RAD)
Rapid Application Development emphasizes short cycles, prototyping and reuse. Speed is valuable only if quality and long-term maintainability are not deferred indefinitely.
1.24. Incremental Model
An incremental model delivers the system in usable slices. Each increment should preserve architecture and integration discipline; otherwise increments simply accumulate incompatible local solutions.
1.25. Spiral Model
The spiral model is risk-driven. Each cycle identifies objectives, analyzes risks, develops/validates a solution and plans the next cycle. It is especially useful conceptually when technical risk should determine what to prototype or prove first.
1.26. Agile Development
Agile approaches emphasize short feedback cycles, working software, collaboration and adaptation. They do not remove architecture, testing or documentation; they move decision-making toward continuous evidence and incremental delivery.
1.27. DevOps and Continuous Delivery
DevOps extends responsibility across development and operations through automation, shared feedback and operational ownership. Continuous integration and delivery reduce batch size between code change and deployable artifact.
A high-frequency pipeline is useful only when tests, deployment controls, observability and rollback are reliable enough to keep change risk bounded.
1.28. CASE Tools
Computer-Aided Software Engineering historically grouped modeling, code generation, documentation and project-support tools. Modern IDEs, CI systems, static analyzers, issue trackers and model/code generators can be understood as a more distributed engineering toolchain.
1.29. Extreme Programming and the Real Boundary of the Planning Game
Extreme Programming (XP) is not merely short iterations or frequent meetings. Its historical contribution is the attempt to combine requirements feedback and engineering practice in the same short cycle. Small releases, test-first development, continuous integration, refactoring, pair programming, and collective code ownership all aim to shorten feedback latency.
The planning game deliberately separates technical estimation from business priority:
customer / product side -> value, priority, scope decisions
engineering side -> technical cost, risk, estimation
together -> stories selected for the iterationThe separation matters. Engineers should not unilaterally define business value, and business stakeholders cannot safely ignore technical cost. A user story is also not a complete requirements specification; it is a compact delivery unit that starts a conversation and is refined through examples and acceptance criteria.
XP and Scrum are not synonyms. Scrum is primarily a framework for roles, transparency, and iterative work management, while XP includes explicit engineering practices. Teams can combine them, but the concepts should not be collapsed.
In regulated, safety-critical, or security-critical development, story cards alone should not be assumed to provide sufficient traceability. Requirement identity, design rationale, verification evidence, risk, and change history may still need controlled records. Agility means shortening feedback and evidence cycles, not eliminating evidence.
The value of XP is therefore not a mechanical checklist of practices. It is the combination of technical discipline and rapid feedback that keeps change affordable. Short iterations alone do not create agility when tests are weak, integration is infrequent, or design debt is uncontrolled.
Unit 2: Software Project Management
Project planning starts by defining deliverables and their acceptance criteria. Establish dependencies among work packages and, where uncertainty is significant, express estimates as justified ranges rather than unsupported point values. The critical path is not a one-off schedule calculation: recompute the effect of delays on downstream work. For a change request, assess scope before comparing its effects on time, cost, quality, and risk.
2.1. Purpose of Project Management
Project management coordinates scope, time, cost, people, quality and risk. Software projects are difficult because progress is less physically visible than in many engineering disciplines and because late integration can expose hidden dependencies.
2.2. Effective Project Management
Effective project management maintains a decision-ready view of scope, dependencies, staffing, quality objectives, and material risks. Work completion is distinguished from evidence of an accepted deliverable. A change to one constraint requires revisiting the others; for example, compressing the schedule can increase parallel work, integration cost, and defect risk.
2.3. Project Resources
Resources include:
- people and expertise,
- computing environments,
- licenses/tools,
- test hardware/data,
- time,
- external suppliers and dependencies.
Adding people to a late project can increase coordination and onboarding cost; staffing is not a linear throughput control.
2.4. Team Organization
Teams need clear ownership and communication paths. Centralized, decentralized and mixed structures have different strengths. Architecture ownership should not mean one person becomes a throughput bottleneck; decisions and interfaces must be documented enough for collective execution.
2.5. Software Measurement
Direct measures include values such as elapsed time, defect count, build duration and resource consumption. Indirect measures try to represent qualities such as maintainability or complexity.
Metrics can distort behavior when treated as targets without context. Lines of code, commit count or ticket closure count are particularly poor measures of individual productivity.
2.6. Direct Measurements
Direct measurements observe a quantity without first estimating it from another project attribute: elapsed time, CPU usage, memory consumption, defect count, code size, or request latency are examples. Definitions matter: mean latency, the 99th percentile, and maximum latency answer different questions. Measurements must record the workload, environment, and collection interval to support comparison.
2.7. Indirect Measurements
Indirect measurements derive a quantity from other observations or a model, such as complexity indices, functional size, defect density, or estimated effort. They can support decisions but inherit uncertainty from measurement rules and assumptions. An indicator must not be treated as a direct observation of maintainability or future productivity.
2.8. Function Points
Function Point Analysis estimates functional size from user-visible inputs, outputs, inquiries, files and interfaces. It was designed to be less language-dependent than LOC.
Its value depends on consistent counting and suitable project type; it is not a universal predictor of engineering effort.
2.9. LOC versus FP
LOC is objective after code exists but varies with language, generation and style. FP estimates functionality earlier but requires interpretation. Both are proxies, not direct measures of value or complexity.
2.10. Cost Estimation
Estimation methods include:
- expert judgment,
- analogy,
- Delphi/wideband consensus,
- parametric models,
- decomposition and bottom-up estimation.
An estimate should include uncertainty rather than presenting one precise number as certainty.
Estimation Uncertainty and Decision Ranges
A software estimate is not a measurement of the future. Function points, LOC-based models, COCOMO-style equations, expert judgment, and historical velocity use different proxies and inherit uncertainty from scope, team, technology, and data quality.
A useful estimate should therefore state assumptions and a range rather than one precise number. Re-estimation after requirements or architecture change is normal engineering behavior, not evidence that the original estimate was necessarily careless.
Risk registers should connect each material risk to an owner, trigger, mitigation, and contingency. A long unprioritized risk list is not a control mechanism.
2.11. Cost Estimation Methodology
Estimation begins with a defined scope, work breakdown, resource assumptions, historical reference class, and uncertainty range. Expert judgment, analogy, parametric estimation, and bottom-up analysis may be combined when their assumptions are explicit. An estimate should be revised after significant requirements, staffing, technology, or architecture changes.
2.12. Expert Judgment
Expert judgment uses experience from comparable projects to estimate work and identify hidden dependencies. Its advantage is speed where reliable quantitative history is scarce; its limitations include anchoring, optimism, and differences between teams. Independent estimates and recorded assumptions make the result more auditable.
2.13. Delphi Method
The Delphi method gathers independent expert estimates, summarizes differences, and conducts further rounds after participants review the anonymous reasoning. The aim is to reduce group pressure and expose disagreement, not to guarantee a correct consensus. Record the final range and reasons for residual uncertainty.
2.14. Analytical Estimation
Analytical or bottom-up estimation decomposes work into implementable components and estimates effort for each component before aggregation. Dependencies, integration, testing, and contingency cannot be omitted merely because they are not isolated features. A work breakdown structure makes the estimate traceable to scope.
2.15. COCOMO
COCOMO is a parametric effort-estimation family based on software size and cost drivers. Its historical forms are useful for understanding nonlinear effort growth. Any real use requires calibration to contemporary technology and organization rather than blindly applying old coefficients.
2.16. Putnam-Norden-Rayleigh Model
The Putnam approach models staffing/effort over time with a Rayleigh-like curve and relates size, effort and schedule. It demonstrates an important principle: aggressively compressing schedule can increase required effort disproportionately.
2.17. Risk Management
A risk is an uncertain event/condition with potential impact. A useful risk record includes probability, impact, trigger/indicator, owner and response.
Risk categories can include:
- product size/scope,
- business/customer,
- process,
- technology,
- development environment,
- staffing,
- external dependency.
Responses include avoid, reduce/mitigate, transfer/share or accept with contingency.
2.18. Risk Categories
A practical risk taxonomy distinguishes product size, business viability, customer decisions, process discipline, technology maturity, development environment, and staffing. Categories support identification; prioritization still requires probability, impact, exposure, and the available mitigation time.
2.19. Product-size Risks
Product-size risks include underestimated data volume, growth in component count, excessive interfaces, and underestimated state-space complexity. Capacity models and early integration tests can expose these risks before scale invalidates architecture assumptions.
2.20. Business Risks
Business risks include budget reduction, shifting organizational priorities, regulatory changes, vendor dependence, and loss of expected product value. Treatment requires an accountable decision owner and a contingency for interrupted funding or changed delivery scope.
2.21. Customer Risks
Customer-related risks arise when requirements decisions arrive late, stakeholders have conflicting expectations, acceptance criteria remain undefined, or domain specialists are unavailable. Decision records and testable acceptance examples reduce ambiguity at integration and release time.
2.22. Process Risks
Process risks include absent change control, unreliable quality gates, weak test automation, and untraceable requirements. A process is effective when it supplies timely evidence and detects deviations, not merely when its documents exist.
2.23. Technology Risks
Technology risks include immature platforms, unknown throughput or latency limits, third-party dependencies, incompatible hardware, and unresolved vulnerabilities. Time-boxed experiments should measure the relevant limitation rather than rely on a feature checklist.
2.24. Development-environment Risks
Development-environment risks include unstable build toolchains, nonreproducible dependencies, unreliable test infrastructure, and test environments that fail to represent production constraints. Reproducible builds and versioned tool configurations reduce uncertainty.
2.25. Personnel Risks
Personnel risks include concentration of critical knowledge, staffing turnover, missing competencies, communication failures, and uneven workload. Reviews, explicit ownership, and accessible technical records mitigate these risks without requiring full interchangeability of specialists.
2.26. Risk Responses
A risk may be avoided, mitigated, transferred, or accepted. A useful risk entry records its trigger, probability, impact, owner, mitigation, and fallback decision. Acceptance is an explicit choice about residual exposure, not the absence of action.
2.27. Software Project Plan
A software project plan connects the problem statement, scope, requirements, architecture approach, work packages, milestones, staffing, quality gates, risks, and change procedure. Estimates and baselines should be versioned so that later decisions can be traced to the assumptions under which they were approved.
2.28. Project Organization
Functional organizations group specialists by discipline; project organizations dedicate teams to a delivery objective; matrix structures share authority and resources across both dimensions. The choice affects ownership, escalation, resource allocation, and the cost of cross-team dependencies.
2.29. Team Structures
Team structure determines how work is coordinated. For n people, the maximum number of pairwise communication links is n(n − 1)/2; this is a possible-link count, not a measured communication workload. Stable responsibility boundaries and explicit interfaces reduce the communication needed to integrate parallel work.
2.30. Project Schedule
A schedule should reflect dependency structure, not simply assign arbitrary dates to every task.
Gantt charts show task timing. PERT/CPM model precedence and critical paths. The critical path contains activities whose delay can delay completion when there is no available float.
2.31. Gantt Chart
A Gantt chart represents planned activities as intervals on a timeline, making milestones and overlaps visible. It is useful for reporting but does not independently encode all resource constraints or causal dependencies. The underlying schedule should retain the precedence network and uncertainty assumptions.
2.32. PERT and Critical Path Method
PERT uses estimated task durations to reason about schedule uncertainty, whereas the critical path method identifies the longest dependency chain for a given duration model. Critical-path tasks have zero total float under the modeled constraints; resource contention can change the actual schedule. Monte Carlo simulation can estimate the probability of meeting a milestone when task durations are uncertain.
Unit 3: Software Quality Management and Testing
Quality assessment begins by converting requirements into observable acceptance conditions. For each important behavior, identify invalid inputs, boundaries, failures, and resource exhaustion in addition to the nominal path. Since coverage is more than executed lines, trace high-risk requirements to evidence of verification. After a defect is corrected, repeat the failing scenario and check related behaviors for regression.
3.1. Software Quality
Quality means fitness relative to explicit and implicit requirements. It includes functional correctness but also performance, reliability, security, maintainability and other properties.
ISO/IEC 25010:2023 defines the current product-quality model with nine characteristics, superseding earlier quality-model formulations.
3.2. Quality Metrics
Metrics should correspond to engineering decisions. Examples include defect escape rate, failure rate, response-time percentiles, resource utilization and code-complexity indicators.
3.3. Halstead Metrics
Halstead metrics derive values from counts of distinct/total operators and operands. They are historically important quantitative code metrics but should not be treated as direct measures of human cognitive difficulty or quality.
3.4. McCabe Cyclomatic Complexity
Cyclomatic complexity measures independent control-flow paths. For a connected control-flow graph:
V(G) = E - N + 2and for structured code it is also related to the number of decision points plus one. High complexity can indicate testing/maintenance difficulty, but a low number does not guarantee good design.
3.5. Quality Assurance
Quality assurance focuses on processes that prevent defects and establish confidence. Quality control evaluates work products and finds nonconformance. Reviews, automated checks and testing can contribute to both depending on context.
3.6. Quality Control
Quality control assesses whether deliverables conform to defined criteria through inspection, test, and measurement. It complements quality assurance, which aims to improve the process that produces deliverables. A passing test is evidence for the behavior exercised, not proof of the absence of every defect.
3.7. Technical Reviews
Code/design reviews find defects, spread knowledge and challenge assumptions before deployment. Effective review has a defined scope and should focus on correctness, maintainability and risk rather than personal style preferences.
3.8. Purpose of Software Testing
Testing executes or evaluates a system to reveal defects and collect evidence against specified behavior. Tests should target observable outcomes, boundaries, faults, and state transitions. Their adequacy depends on the chosen model, test oracle, environment, and the consequences of failure.
3.9. Test Levels
Common levels are:
- unit/component,
- integration,
- system,
- acceptance.
The boundaries depend on architecture, but each level should have a clear purpose and test oracle.
3.10. Unit Testing
A unit test exercises a small component under controlled dependencies. Test doubles/drivers/stubs may isolate collaborators, but excessive mocking can test the mock arrangement rather than real behavior.
3.11. Test Drivers and Stubs
A test driver invokes the component under test and controls its inputs; a stub substitutes a dependency and supplies predetermined outputs. They permit component isolation but can conceal integration defects if the substitute does not preserve the dependency contract. Contract and integration tests must close that gap.
3.12. Integration Testing
Integration tests verify component interactions, protocols, database mappings and lifecycle assumptions.
Historical top-down integration begins from high-level components with lower stubs; bottom-up integration begins from lower components with drivers. Contemporary systems often combine incremental vertical slices with automated contract/integration tests.
3.13. Top-down Integration
Top-down integration connects high-level orchestration first, using stubs for unavailable lower-level services. It exposes control-flow errors early but may delay validation of concrete low-level behavior. Replace stubs incrementally and rerun integration scenarios.
3.14. Bottom-up Integration
Bottom-up integration validates lower-level components and services first, using drivers until their higher-level callers are available. It offers early evidence about foundational behavior but may postpone end-to-end workflow defects. A combined integration strategy can mitigate both limitations.
3.15. System Testing
System tests validate end-to-end system behavior against system requirements. Acceptance testing evaluates whether the delivered system is suitable for stakeholder/business use under agreed criteria.
3.16. Acceptance Testing
Acceptance testing checks a system against stakeholder-approved outcomes and operating constraints. Acceptance scenarios should identify input, preconditions, observable results, and failure behavior; they should not depend on implementation details that are not contractual.
3.17. Debugging
Testing reveals a failure; debugging finds and corrects its cause. Reproducing the failure deterministically, narrowing the state difference and validating the correction are separate steps.
3.18. White-box Testing
White-box techniques use implementation structure. Basis-path testing relates tests to control-flow paths. Loop testing considers zero, one, boundary and repeated iterations according to permitted ranges.
Exhaustively covering every path is generally impossible when loops and conditions combine, so coverage is evidence rather than proof.
3.19. Basis-path Testing
Basis-path testing uses a control-flow graph and cyclomatic complexity to identify a set of linearly independent execution paths. The method supports structural coverage planning, but path execution alone does not guarantee a correct test oracle or coverage of every input value.
3.20. Loop Testing
Loop tests examine zero iterations, one iteration, ordinary repetition, and boundary counts. When a bound exists, cases near that bound are particularly valuable. For nested loops, combinatorial growth requires representative cases chosen from risk and invariants.
3.21. Black-box Testing
Black-box techniques derive tests from externally specified behavior.
Equivalence partitioning groups inputs expected to behave similarly.
Boundary-value analysis targets edges where defects are common.
Cause-effect graphs and decision tables model combinations of conditions and resulting actions.
3.22. Equivalence Partitioning
Equivalence partitioning divides input values into classes expected to yield equivalent behavior. Select representatives from both valid and invalid partitions, then verify that the partition assumption holds at observable boundaries. It reduces redundant test cases but does not replace boundary testing.
3.23. Boundary-value Analysis
Boundary-value analysis targets values immediately below, at, and immediately above a threshold. For an inclusive interval [1, 100], candidates include 0, 1, 2, 99, 100, and 101. Boundary conditions also arise in lengths, timestamps, integer ranges, and concurrency limits.
3.24. Cause–Effect Graphs and Decision Tables
Cause–effect graphs describe logical dependencies between conditions and outcomes. Decision tables enumerate combinations of relevant conditions and expected actions; constraints can eliminate impossible combinations. They are particularly useful for authorization and business-rule logic with interacting predicates.
3.25. Test Planning
A test plan defines scope, environments, data, responsibilities, entry/exit criteria and evidence. Test specifications should identify expected results and preconditions so a passing test means something reproducible.
3.26. Verification and Validation
Verification asks whether the work product satisfies its specification; validation asks whether the resulting system satisfies its intended use. Both activities should occur throughout the lifecycle.
3.27. Regression Testing
Regression tests check that existing supported behavior still works after change. A regression suite should be fast and reliable enough to run frequently; flaky tests reduce trust and therefore reduce actual protection.
3.28. Continuous Testing
Modern CI pipelines execute tests, static analysis and packaging automatically on changes. Production confidence also requires integration, performance, security and deployment verification that may run at different cadences.
Performance requirements must become testable contracts. “The system must be fast under heavy traffic” is not an acceptance criterion. Specify the dataset size and endpoint mix, offered arrival rate, maximum acceptable failure ratio, p99 bound, and sustained observation period. A test that passes without proving that the load generator delivered its target workload has not verified the requirement.
Distinguish a short CI regression check from a full capacity experiment. The latter needs realistic database, network, and resource boundaries; the former can detect algorithmic regressions and large slowdowns on each change. Generator resource use, valid-response assertions, and repeatability of the measured result belong in the test strategy.
Unit 4: Maintenance and Configuration Management
4.1. Maintenance
Maintenance includes:
- corrective changes,
- adaptive changes,
- perfective improvements,
- preventive refactoring/hardening.
The classification is useful, but one change can belong to more than one category.
4.2. Installation and Field Support
Delivery includes installation, migration, configuration and operational support. A product that works only in the developer environment is not production-ready.
4.3. Maintenance Process Model
A maintenance process receives and classifies a change request, analyzes impact and risk, selects a baseline, implements a bounded modification, and verifies the affected behavior. Release control, documentation updates, and rollback arrangements complete the cycle. Emergency fixes should remain traceable after deployment.
4.4. Maintainability
Maintainability improves when modules have clear responsibilities, interfaces are stable, tests cover behavior and operational diagnostics identify failures quickly.
4.5. Reducing Maintenance Cost
Useful practices include:
- simple architecture,
- controlled dependencies,
- readable code,
- automated tests,
- reproducible builds,
- documented data migrations,
- compatibility policies,
- observability.
4.6. Software Configuration
A configuration identifies exact versions of source, dependencies, build inputs, deployment parameters and sometimes infrastructure/data schema required to reproduce a system state.
4.7. Configuration Management
SCM controls:
- identification,
- versioning,
- change control,
- status accounting,
- audit/release baselines.
4.8. Baseline
A baseline is an agreed configuration used as a reference for further change. In modern delivery, signed/versioned build artifacts and immutable releases often play this role.
4.9. Change Control
Not every change needs a committee, but every production-impacting change needs an accountable path: why it exists, what it changes, how it was verified and how it can be reverted or mitigated.
4.10. Configuration Audit
An audit checks whether delivered artifacts correspond to the declared configuration. Status reporting records which change/version is in which environment and whether required controls have completed.
4.11. Status Reporting
Configuration status reporting records which baselines, approved changes, build artifacts, and releases are currently in effect. It enables teams to answer which change entered a release and what verification evidence accompanied it. Status records must be consistent with the source and artifact repositories.
4.12. Version Control
Distributed version-control systems make branching/merging inexpensive, but repository history alone is not release management. Builds must still map commits to reproducible artifacts and deployment state.
4.13. Modern Configuration Chain
A contemporary chain can be:
source commit
↓
locked dependencies
↓
reproducible build
↓
versioned artifact/container
↓
environment configuration
↓
deployment recordSecrets should not be embedded into source or ordinary configuration artifacts.
4.14. Reproducible Configuration for AI and machine-learning Systems
A software release that contains AI or machine learning cannot always be identified by a source-code commit alone. The same code can produce different behavior with different data, preprocessing rules, model weights, prompts, or decision thresholds.
A reproducible release context may therefore include:
source code
+ data / dataset version
+ preprocessing and feature rules
+ schema / label contract
+ model or checkpoint
+ training and evaluation configuration
+ prompt / template version
+ evaluation set
+ decision thresholds
+ deployment configuration
= reproducible execution contextNot every project requires every item. The durable rule is that artifacts capable of changing system behavior must be visible configuration items.
4.12.1. Data and Model Provenance
If it is unknown which data, preprocessing version, and source revision produced a model, reproducing a result or locating a regression becomes difficult. Dataset versions, model artifacts, and source commits should therefore be traceable to one another rather than encoded only in filenames.
data version
↓
training configuration
↓
model artifact
↓
evaluation result
↓
deployed releaseThis lineage answers more than “which model had the best score?” It records why a model was released and what evidence supports rollback or replacement.
4.12.2. Evaluation Data Is Configuration Too
Changing the evaluation set can change the measured result of an unchanged model. Comparisons should therefore record the evaluation-data version, filtering rules, and metric definition. Repeatedly tuning against the same test set can leak test information into development and weaken its role as independent evidence.
The testing perspective is developed further in Software Test Engineering.
4.12.3. Separating Training and Serving Behavior
When production input preparation diverges from the training pipeline, training-serving skew can appear. Normalization, category mapping, tokenization, and feature-generation contracts should be traceable across both environments.
Versioning a model file alone does not solve this problem. The model's expected input and output contracts are also part of the release.
4.12.4. Gradual Rollout and Rollback
A new model or threshold does not always need to receive all production traffic immediately. Depending on risk, a release can begin with a limited cohort, shadow evaluation, or controlled comparison. Such rollout is useful only if the previous artifact and configuration remain recoverable.
new release
→ limited validation
→ measurement
→ expand or roll backThis does not separate model development from configuration management. It extends configuration management to data and model artifacts. Experiment design, model selection, and production observation are discussed from the model-development perspective in Artificial Neural Networks and Learning Models.
Unit 5: Software Requirements Analysis
5.1. Requirements
Requirements describe behavior or constraints expected from the system.
Functional requirements specify what the system does.
Non-functional requirements specify qualities/constraints such as latency, availability, security, capacity, auditability or portability.
A statement such as "the system shall be fast" is not sufficiently verifiable. A requirement should define measurable conditions where the property matters.
5.2. Functional Requirements
Functional requirements state what behavior the system must provide under specified inputs and states. A testable statement identifies the trigger, preconditions, data, expected output, and exceptional outcome. Functions must be distinguished from proposed implementation mechanisms.
5.3. Non-functional Requirements
Architecture should make nonfunctional requirements measurable. Latency, throughput, availability, recovery time, consistency, security, deployability, and modifiability can pull the design in different directions.
A quality-attribute scenario states a source, stimulus, environment, artifact, response, and response measure. This turns statements such as “the system should be fast” into testable constraints.
Architecture fitness functions can automate part of this contract: dependency rules, latency budgets, schema compatibility, forbidden calls, resource ceilings, or security invariants can be checked continuously.
The goal is not to score architecture with one number. It is to make important design assumptions observable before production failure reveals them.
5.4. Requirements Specification
A specification should be correct, consistent, sufficiently complete, unambiguous, traceable and verifiable for its intended use. These are goals; real projects manage uncertainty rather than pretending it does not exist.
5.5. Requirement versus Design
"The system shall authenticate users" is a requirement. "Use protocol/library X" is usually a design constraint unless a stakeholder/regulation specifically requires it.
Separating need from implementation preserves design freedom.
5.6. Traceability
Traceability links:
stakeholder need
-> requirement
-> design element
-> implementation
-> verification evidenceThis is valuable in regulated or critical systems and also when impact analysis is difficult.
Airborne software is a concrete example of this evidence chain. Preserving links among requirements, design decisions, source code, verification results, configuration, and problem reports is a core part of software assurance. The DO-178C and avionics context is discussed in Avionics Systems and Unmanned Aerial Vehicles.
5.7. System-level Safety Constraints: Requirements Are More Than Feature Statements
In a critical system, a requirement should not only describe what the system shall do. It may also need to state what the system must not do under a particular context. Components can satisfy their local specifications while their interaction still produces an unacceptable system state. For safety-critical and cyber-physical systems, requirements can therefore be derived backward from system-level losses and hazards.
unacceptable outcome
↓
hazardous system state
↓
constraint that must not be violated
↓
design decision
↓
verifiable requirement
↓
test / operational evidenceThis does not replace functional or non-functional requirements. It exposes an additional dimension: a function that is correct in isolation can still be unsafe when executed in the wrong state, at the wrong time, or on stale feedback.
A requirement such as send command, for example, is incomplete when command validity depends on operating mode, data age, acknowledgement, physical state, or another controller acting on the same process. Those conditions belong to the engineering contract as much as the command payload itself.
This view is consistent with Nancy G. Leveson's systems-theoretic treatment of safety through control and constraints. The objective here is not to reproduce a method mechanically, but to distinguish a feature inventory from a specification of safe system behavior.
Traceability consequently becomes stronger than a simple requirement -> test link:
risk / hazard
-> system constraint
-> requirement
-> architectural element
-> implementation
-> verification evidenceWhen a requirement changes, the originating constraint and the evidence that justifies the behavior should be reconsidered as well.
5.8. Studying the Existing System
Legacy behavior is often an undocumented source of requirements. Existing workflows, database constraints, operational scripts and failure workarounds should be investigated before replacement.
5.9. Elicitation Techniques
Techniques include interviews, workshops, observation, questionnaires, document analysis, prototypes and analysis of actual production behavior.
Stakeholders frequently describe desired solutions rather than underlying needs; elicitation should distinguish the two.
5.10. Data Modeling
Conceptual data models identify entities, relationships, lifecycle and constraints before physical schema decisions.
5.11. UI Prototyping
UI prototypes expose workflow and terminology uncertainty early. Visual fidelity should match the question being tested; a low-fidelity workflow prototype is often more informative early than a polished mockup.
5.12. Evaluating the Analysis Model
Check whether every requirement is represented, conflicts are visible, system boundaries are clear and exceptional/error cases are modeled.
5.13. Formal Specification
Formal methods express requirements using mathematically defined notations. They are valuable when ambiguity or proof obligations justify the cost, particularly in protocols, safety properties and critical algorithms.
5.14. Relational Notations
Relational notations specify constraints among entities, sets, and associations. In formal specification, predicates can express invariants and preconditions without prescribing an implementation sequence. The specification should make invalid states distinguishable from merely unimplemented features.
5.15. State-oriented Notations
State machines describe lifecycle transitions. Transition tables make event/state combinations explicit and are effective for protocols and control systems.
5.16. Decision Tables
Decision tables enumerate combinations of conditions and actions. They help expose missing and contradictory business-rule combinations.
5.17. Transition Tables
Transition tables express state-dependent behavior by listing the current state, triggering event, guard condition, action, and next state. Undefined or prohibited transitions must have explicit outcomes. Tables support systematic derivation of state-machine tests.
5.18. Petri Nets
Petri nets model concurrency, synchronization and resource flow through places, transitions and tokens. Reachability and liveness analysis can expose deadlock-like behavior in modeled systems.
5.19. Principles of Requirements Analysis
Requirements analysis defines the problem boundary, external actors, relevant data, operations, failure cases, and measurable quality constraints before selecting a solution. Ambiguities should be resolved through domain examples and acceptance criteria, with assumptions recorded for later verification.
5.20. Data-flow Analysis
Data-flow diagrams model external entities, processes, data stores and flows. They are historically important structured-analysis tools and remain useful when the problem is primarily transformation of information.
5.21. Data Flow Diagrams
Data flow diagrams distinguish external entities, processes, data flows, and data stores. A context diagram treats the whole system as one process; successive levels refine it while preserving balanced external data flows. DFDs describe transformation and movement, not execution scheduling or object lifecycles.
5.22. Data Dictionary
A data dictionary defines names, meanings, structure, units, valid ranges and ownership of shared data elements. Modern API/schema registries and typed contracts continue the same discipline.
5.23. Process Specifications
A process specification supplements a data flow diagram with the internal decision logic of a process. Structured natural language, pseudocode, decision tables, and decision trees can describe the same behavior at different levels of precision. Preconditions and error cases must remain explicit.
5.24. Historical Data-structure-oriented Methods
Warnier–Orr and Jackson-style methods historically derived program structure from the shape and sequencing of data. They remain relevant for understanding structured transformation problems, although modern services commonly favor explicit domain models, typed contracts, and event-driven flows.
5.25. Unifying Reliability and Security at the Requirements Level
Security and reliability are not independent bolt-on features; both shape how a system contains faults and abuse. In software architecture, resource quotas, the blast radius of a change, component and team privilege boundaries, secure defaults, rollback paths, and post-incident learning belong to the same delivery design; leaving one of them undefined can let a small change create disproportionate system-level risk. In denial-of-service scenarios in particular, the protection mechanism itself must not consume the availability it is intended to preserve.
- State scenarios in which availability, confidentiality, and integrity goals conflict.
- Make resource limits, fail-safe behavior, and recovery objectives measurable non-functional requirements.
- Design changes together with blast-radius and rollback strategy.
Refining a requirement into a testable invariant. “Processing the same request twice must not create duplicate records” is unverifiable until the scope of repetition is defined. First identify the business key that defines uniqueness and the permitted retry window. An acceptance criterion can then state: “Successful retries with the same key and payload produce one logical result; a different payload with the same key must not silently overwrite it.” Execute a successful request, simulate losing its response, and retry the identical request; neither persisted record count nor external side-effect count may increase. Test two concurrent requests racing on the same key separately. Without jointly considering uniqueness constraints, transaction boundaries, and side effects, comparing HTTP responses cannot establish idempotency. This turns a user-level statement into observable behavior testable under failure.
Unit 6: Software Design
6.1. Purpose of Design
Design decides how responsibilities, state and interfaces will satisfy requirements while controlling change and operational risk.
6.2. Architecture and Data Design
Architecture defines major components, deployment boundaries and interactions. Data design defines ownership, schema, consistency and lifecycle of information. Treating persistent data as an afterthought creates coupling that is difficult to remove later.
6.3. Software Architecture
Architecture assigns responsibilities to components, defines their interactions, and establishes system-level constraints such as consistency, fault isolation, security, throughput, and latency. Choosing a deployment topology without identifying its failure and ownership boundaries does not complete the architecture.
6.4. Modularity
A module should have a coherent responsibility and a small explicit interface. Modularity is valuable when it reduces the number of components affected by one change.
A domain boundary is not established merely by arranging classes into packages; it determines which module owns rules, data, and change decisions. Two modules can use the same customer identifier without sharing the same customer lifecycle or validation rules. If a requirement change repeatedly forces coordinated edits across many modules, the information-hiding boundary deserves review. A module contract covers not only method signatures but also meaningful failure states, consistency expectations, and version compatibility.
6.5. Cohesion and Coupling
High cohesion means related behavior belongs together. Low coupling means modules depend on few stable details of one another. These are design objectives, not absolute numeric properties.
6.6. Abstraction
Abstraction exposes a useful model; information hiding conceals decisions likely to change. A stable interface should not leak internal storage, protocol or timing details unless callers genuinely need them.
6.7. Information Hiding
Information hiding keeps decisions likely to change behind stable interfaces. Clients depend on observable contracts rather than storage layout, algorithm details, or infrastructure configuration. This reduces the change surface and makes independent verification possible.
6.8. Structured Programming
Sequence, selection and iteration provide disciplined control flow. Structured programming historically replaced arbitrary branch-heavy programs and remains a foundation even when modern languages add exceptions, async control and functional constructs.
6.9. Preliminary Design
High-level design identifies components and interfaces. Detailed design defines algorithms, state and local contracts. The right amount of detail is what makes independent implementation and verification possible without freezing irrelevant choices.
6.10. Detailed Design
Detailed design specifies algorithms, interfaces, data structures, error paths, state ownership, and concurrency rules sufficiently to support implementation and review. It should identify externally observable contracts and leave implementation flexibility where correctness does not require one fixed technique.
6.11. Design Principles
Design principles include separation of concerns, explicit boundaries, low coupling, high cohesion, abstraction, and controlled dependencies. A design decision should state the constraint it protects and the cost it introduces; additional layers are not inherently an improvement.
6.12. Structural Design and Historical Flow Transformations
Structured-design methods transformed data-flow models into module hierarchies. Their specific notation is historical, but the idea of deriving component boundaries from information flow remains useful.
6.13. Detailed Design Notations
Pseudocode, decision tables, flowcharts, state machines, and contracts can express detailed behavior. Choose the notation according to the question: control flow, logical combinations, lifecycle constraints, or input/output invariants. A diagram is useful only if it preserves precise semantics.
6.14. Common Subsystems
Common subsystems provide cross-cutting capabilities such as authentication, authorization, data access, backup, auditing, and error handling. Their interfaces should separate domain behavior from infrastructure policy while making operational dependencies and failure outcomes explicit.
6.15. Design Review
Design review examines interfaces, data ownership, concurrency, failure paths, security boundaries, and the evidence for performance assumptions before implementation costs rise. Review findings should be linked to specific risks and decisions, not to stylistic preference alone.
6.16. Coupling and Cohesion
Coupling measures dependencies among modules, whereas cohesion concerns how closely related the responsibilities within a module are. Lower coupling and clearer responsibility improve change isolation, but introducing a new abstraction without a stable boundary may increase overall complexity.
6.17. Design Notations
Pseudocode, flowcharts, structure charts, UML diagrams and interface definitions should be used when they make a decision clearer. Producing diagrams with no decision content is not documentation quality.
6.18. User-interface Design
UI design should account for user tasks, feedback, errors, accessibility, consistency and latency. A technically correct backend can still produce an unusable system if interaction state is ambiguous.
6.19. User-interface Problems
Frequent interface failures include inconsistent commands, ambiguous errors, hidden system state, irreversible actions without confirmation, overloaded screens, and weak keyboard accessibility. The design must support recovery and preserve the meaning of user actions across screen sizes.
Unit 7: Object-Oriented Analysis and Design
7.1. Object-oriented Approach
Objects combine state with operations; classes define families of objects. Encapsulation, inheritance and polymorphism are mechanisms, not goals by themselves.
7.2. Objects
An object combines identity, state, and behavior under an explicit contract. Object identity is distinct from equality of attribute values. Ownership and lifetime must be defined when references are shared among components.
7.3. Classes
A class defines a family of objects through fields, operations, and invariants. Visibility and construction rules restrict how objects can enter invalid states. Class structure should model domain responsibility rather than mirror database tables mechanically.
7.4. Encapsulation
Encapsulation restricts direct manipulation of state and exposes operations that preserve invariants. Private fields alone are insufficient if methods permit inconsistent transitions or leak mutable internal references.
7.5. Inheritance
Inheritance expresses substitutable specialization where a derived type preserves the behavioral contract of its base type. Using inheritance only to share implementation can create fragile hierarchies and violate substitutability.
7.6. Polymorphism
Polymorphism lets clients invoke a common contract while implementations vary. Static and dynamic dispatch have different performance and extension characteristics; contract compatibility matters more than the choice of dispatch mechanism.
7.7. Object-oriented Analysis
Object-oriented analysis identifies domain concepts, responsibilities and interactions without prematurely deciding every implementation class.
Historical Coad-Yourdon and Booch methods influenced later UML-based modeling.
7.8. Coad–Yourdon Approach
Coad–Yourdon analysis distinguishes classes and objects, structures, attributes, services, and subject areas. The historical approach is useful for separating domain concepts from the eventual programming-language representation.
7.9. Booch Approach and UML
Useful diagrams include:
- class diagrams for structural relationships,
- sequence diagrams for time-ordered interaction,
- state-machine diagrams for lifecycle behavior,
- activity diagrams for workflow,
- package/component diagrams for dependency/architecture views,
- use cases for actor-goal interactions.
A UML diagram should answer a question; not every project needs every diagram type.
The Booch method contributed iterative object-oriented design practices and a notation for classes, objects, and their interactions. UML subsequently consolidated notation across several object-oriented modeling approaches.
7.10. UML Class Diagrams
A UML class diagram describes classifiers, attributes, operations, visibility, multiplicities, and relationships. A diagram should distinguish inheritance, association, aggregation, and composition rather than treat all edges as equivalent dependencies.
7.11. Sequence Diagrams
Sequence diagrams show the time order of messages among lifelines. They clarify ownership of orchestration, synchronous versus asynchronous interactions, and alternative or exceptional exchanges. Message order must be consistent with the stated concurrency model.
7.12. State-machine Diagrams
State-machine diagrams define states, triggering events, guards, and transitions over an object's lifecycle. Guards should be mutually understandable and invalid transitions explicit. The model can generate transition and invariant tests.
7.13. Activity Diagrams
Activity diagrams represent control or workflow, including decisions, forks, and joins. They are useful for business processes and concurrent activities, but an activity diagram alone does not define data consistency or transaction boundaries.
7.14. Package Diagrams
Package diagrams group model elements and expose dependencies between packages. They help review layering and forbidden dependency directions before implementation.
7.15. Component Diagrams
Component diagrams describe replaceable system parts, provided and required interfaces, and wiring relationships. They are useful for analyzing subsystem boundaries independently of the deployment topology.
7.16. Use Cases
A use case models an actor's interaction with a system to achieve an observable goal. It states preconditions, main scenario, alternatives, and postconditions. Include and extend relationships should express reuse or optional variation, not replace readable behavior descriptions.
7.17. CRC Cards
Class-Responsibility-Collaborator cards are a lightweight technique for discussing what a class owns and whom it must collaborate with. Their value is in design conversation, not the physical card format.
7.18. Relationships
Association, aggregation/composition, dependency and inheritance communicate different semantics. Composition expresses ownership/lifetime more strongly than a generic association.
7.19. Concurrency and Subsystem Design
Concurrent objects/components need explicit synchronization and ownership of mutable state. Hiding a lock inside every object does not automatically produce a deadlock-free system; lock ordering and cross-component operations matter.
7.20. Design Patterns
Patterns name recurring design structures. They are not mandatory templates. A pattern is useful when the forces that motivated it are present; using patterns for their own sake adds indirection.
7.21. Completing the Design
An object-oriented design is ready for implementation when responsibility, lifecycle, collaboration, persistence, concurrent access, failures, and testable contracts are resolved to an appropriate level. A class diagram without these decisions is not a complete design.
7.22. SOLID Connection
Later object-oriented design discussions often use SOLID principles:
- single responsibility,
- open/closed,
- Liskov substitution,
- interface segregation,
- dependency inversion.
They are heuristics for managing change and substitution. They do not override simpler design when the abstraction has no real variability.
7.23. Composition over Inheritance
Composition often reduces coupling to a base-class implementation and allows behavior to be assembled explicitly. Inheritance remains appropriate for stable substitutable type relationships.
7.24. Limits of Object Orientation
Not every problem is best decomposed into stateful objects. Data-oriented, functional, relational, actor/message and pipeline designs can be better depending on workload and invariants.
Unit 8: Implementation and Coding
Implementation converts design into executable artifacts while preserving interface and quality constraints.
8.1. Implementation Phase
Implementation turns approved design and contracts into an executable system. Compilation, static analysis, unit and integration tests, dependency control, and reproducible builds provide evidence that the result corresponds to the intended baseline.
8.2. Software Development Environments
Tooling should provide reproducible compiler/runtime versions, diagnostics, tests and build automation. Language selection depends on ecosystem, runtime, performance, safety, platform and maintainability constraints.
8.3. Programming Languages
Language selection depends on the execution environment, concurrency and memory model, available toolchain, portability, team expertise, and integration constraints. Expressiveness alone does not determine suitability for a latency-sensitive or safety-critical component.
8.4. Database Management Systems
A third-party component saves development cost only if its lifecycle, license, security and operational behavior are acceptable. Dependencies are part of the system and must be versioned and monitored.
8.5. Reusable Libraries and Components
Libraries and reusable components reduce duplicate implementation effort but introduce compatibility, versioning, vulnerability, and licensing obligations. A dependency should be evaluated for its API contract, maintenance status, transitive dependencies, and effect on deployment reproducibility.
8.6. Coding Style
Style exists to make code consistent and reduce cognitive noise. It should prioritize clarity, correctness and automated formatting/static checks over subjective preference.
8.7. Exception/error Handling
Errors should be represented at the right abstraction level. Low-level exceptions can be translated into domain errors, but diagnostic context should not be destroyed. Recovery is appropriate only when the current layer has enough information to recover correctly.
8.8. Code Review
Review should examine invariants, boundary behavior, concurrency, security, tests and maintainability. Automatable formatting/lint issues should be handled by tools so human review can focus on reasoning.
Unit 9: Software Architectures
9.1. Architectural Approach
Architecture defines system boundaries, data ownership, communication and deployment relationships. Architectural decisions are expensive to reverse when they affect persistence or external contracts.
9.2. Client-server
A client requests services from a server. The model says little about physical scale; both can be processes on one host or distributed across networks.
9.3. Two-tier Architecture
A two-tier information system often connects client logic directly to a database/service. Three-tier separates presentation, application/domain logic and data management.
The separation can improve deployability and policy control, but extra tiers add latency and operational complexity.
9.4. Three-tier Architecture
A three-tier architecture separates presentation, application logic, and data management into distinct logical responsibilities. Layers need not be separate network processes: physical distribution changes latency, fault propagation, transaction scope, and operations cost.
9.5. N-tier and Distributed Architectures
N-tier systems split responsibilities further. Distribution should follow real scaling, isolation or organizational needs. A network call is slower and less reliable than an in-process call and introduces partial failure.
9.6. Web Architecture
Web applications combine HTTP, browser/client state, application services, caching and persistent storage. Stateless server endpoints can simplify scaling, but sessions and transactions still create state somewhere in the system.
Moving a bottleneck is not necessarily an architectural gain. Splitting an in-process call into services adds serialization, network latency, timeouts, and retry semantics. Decomposition is justified when independent scaling, fault isolation, or ownership benefits exceed that cost. Allocate the critical user journey's p99 budget among network, application processing, database access, and queueing delay.
Moving work behind an asynchronous queue may shorten acknowledgement time while leaving end-to-end job completion unchanged or worse. Treating “accepted” as equivalent to “completed” changes the functional contract incorrectly. Model first-response latency and final-completion latency separately, including failure and replay behavior.
9.7. Historical WAP
WAP-era architectures adapted web-style services to constrained mobile devices and networks. Their historical importance is in showing how protocol stacks respond to bandwidth/device constraints; contemporary mobile/web systems use different standards.
9.8. Component-based Architecture
Components expose defined interfaces and can be developed/deployed/reused with controlled dependencies. Component boundaries should correspond to cohesive behavior, not arbitrary packaging.
9.9. SOA and Microservices
Service-oriented architecture organizes capabilities behind service contracts. Microservices emphasize independently deployable bounded services, decentralized ownership and operational automation.
Splitting a monolith into network services does not automatically improve architecture. It introduces distributed transactions, observability, versioning, retries, latency and deployment complexity. A modular monolith is often the better starting point when independent scaling/deployment is not required.
9.10. Production Behavior: Architecture Is More Than the Happy Path
An architecture diagram can show services, data stores, and connections, yet the most important production behavior often appears when a dependency slows down, becomes partially unavailable, or approaches saturation. Architectural evaluation should therefore include failure paths and recovery paths, not only the successful request path.
A slow dependency can produce a chain such as:
dependency latency
-> resources held for longer
-> more concurrent in-flight work
-> thread / pool / connection pressure
-> queue growth
-> higher tail latency
-> timeouts
-> retries create additional traffic
-> a local problem becomes systemicNone of these steps is necessarily a defect in isolation. The architectural problem appears when one layer can absorb another layer's failure or capacity behavior without a bound. Timeouts, bounded queues, concurrency limits, load shedding, circuit breakers, and bulkheads are therefore not merely library features; they are mechanisms for limiting a failure domain.
9.9.1. Slowness Is Also a Failure Mode
A dependency that rejects a connection is usually obvious. A dependency that still responds but becomes progressively slower can be more dangerous because calls keep resources occupied while the system continues to look nominally available.
Health assessment may therefore need more than is the process running?:
response correctness
+ latency budget
+ dependency saturation
+ queue depth
+ recovery behavior9.9.2. Isolation Is Not the Same as Optimization
Sharing every worker, thread, or connection budget can maximize average utilization, but it can also allow an expensive low-priority workload to consume resources required by a critical path. Separating resource or concurrency budgets may reduce theoretical peak utilization while improving predictability and reducing blast radius.
The trade-off is therefore broader than maximum throughput:
throughput
↔ isolation
↔ latency predictability
↔ recovery time9.9.3. Steady State Is a Production Requirement
A system is not stable merely because it survives a short benchmark. If queues, temporary files, logs, sessions, cache keys, native buffers, or connections accumulate over time, the system is consuming future capacity.
A stable lifecycle should converge back toward an acceptable operating band:
load arrives
-> system responds
-> load subsides
-> backlog drains
-> temporary resources are released
-> the system returns to its safe operating bandProduction-ready architecture therefore treats resource lifecycle and recovery behavior as part of the architectural contract, not as post-deployment cleanup.
9.11. From a Modular Monolith to Distributed Services: Moving a Boundary Transfers Cost
The distinction between a monolith and microservices is more than the number of processes used to run an application.
The deeper architectural decision is:
which responsibility
which data
which failure boundary
which deployment boundary
belongs together?Moving a module from an in-process boundary to a remote service does not remove its business logic. It transfers the boundary and creates a different set of costs.
9.10.1. Modularity and Distribution Are Not the Same Property
One deployable application can still have strong module boundaries:
application
├── identity
├── orders
├── inventory
└── reportingEach module can define its responsibility, exposed entry points, internal implementation, and permitted dependencies.
Conversely, a system with dozens of services can remain tightly coupled if every service accesses the same tables directly, must be deployed together, or participates in long synchronous call chains.
Therefore:
many processes ≠ low coupling
one process ≠ high coupling9.10.2. A Process Boundary Creates a New Failure Boundary
An in-process call is roughly:
caller
↓
function / method
↓
return / exceptionMoving the call across a network adds serialization, network transport, remote queues or workers, remote dependencies, and a response path.
The design must now address connection failure, lost responses after successful execution, retries with uncertain completion, deadline ownership, version compatibility, partial failure, and end-to-end tail latency.
Turning a method call into a network call adds not only latency but also uncertainty.
9.10.3. Assessment of Deployment Independence
Independent deployment can be a strong reason to create a service boundary.
But if a change to one service requires several others to be deployed in the same release train, physical separation has not created meaningful deployment independence.
True independence requires contracts that can evolve with some degree of backward compatibility.
If every change to one service requires many others to be released at the same time, the architecture may have become a distributed monolith.
9.10.4. Data Ownership Is Central to a Service Boundary
Code is relatively easy to separate. Data boundaries are harder.
If two services update the same tables directly, ownership of schema, invariants, migration order, lock behavior, and field meaning becomes ambiguous.
A strong service boundary usually combines:
behavior ownership
+
data ownership
+
change ownershipThis does not imply a universal rule that every microservice must run on a physically separate database server. The important property is explicit ownership of data changes and an explicit path for crossing the boundary.
9.10.5. Distributing a Local Transaction Changes the Shape of the Problem
A local database transaction can preserve several state changes inside one atomic boundary.
When those responsibilities become separate services, one local ACID transaction no longer naturally spans the complete workflow.
The architecture must now define what happens when a later step fails, whether an earlier step can be compensated, what happens when a message arrives twice, how temporary unavailability is represented, and whether the workflow is complete or still in progress.
Patterns such as saga, outbox, idempotency, and compensation manage the failure model introduced by distribution. They are not free capabilities obtained by splitting the application.
local transaction
↓ distribution
distributed workflow
↓
state machine + failure handlingThat additional machinery is part of the architectural cost.
9.10.6. A Network Call Makes Coupling More Expensive
An unnecessary dependency between two modules can remain hidden inside a monolith because an in-process call is cheap.
Moving the same dependency over a network exposes latency, serialization, timeouts, retries, connection management, observability, versioning, and partial failure.
This does not make microservices inherently poor architecture. If a process boundary serves a real scaling, isolation, organizational, or security requirement, the cost can be justified.
Distribution simply does not automatically repair bad module boundaries.
9.10.7. Fan-out Can Amplify Tail Latency
Suppose one user request synchronously depends on several services. The response often cannot complete until the slowest required dependency finishes.
Even when each dependency is only occasionally slow, increasing fan-out increases the probability that the user encounters at least one slow dependency.
Average service latency is therefore insufficient. End-to-end P95, P99, timeout budgets, fan-out, and retry amplification must be considered together.
The quantitative performance model is developed further in High-Performance Java Data Systems.
9.10.8. A Retry Is a Mechanism That Creates New Traffic
Retrying a remote failure may improve reliability.
However, when overload causes the timeout, a positive feedback loop is possible:
overload
↓
timeout
↓
retry
↓
more load
↓
more timeoutA distributed architecture therefore makes timeout, retry, backoff, jitter, idempotency, and load-shedding policies part of architecture rather than implementation detail.
9.10.9. The Operational Tax
An independently deployed service carries more than its business logic.
Builds, artifacts, configuration, secrets, deployment, health, logging, metrics, tracing, alerting, capacity, backup and recovery, security updates, and version compatibility all become part of its lifecycle.
Ten services do not merely mean ten collections of source code. They can mean ten operational lifecycles.
Automation can reduce this cost substantially, but it cannot make the cost disappear.
9.10.10. Organizational Boundaries Matter as Much as Technical Ones
If two services are always changed by the same team, always released together, and each contains part of the same business rule, the split may not represent a natural boundary.
A service boundary can be more valuable where different areas have different rates of change, scaling requirements, security boundaries, or release lifecycles.
Architecture is not only a topology of CPU and memory. It also shapes the topology through which people create change.
9.10.11. A Modular Monolith Does Not Have to Be Temporary
A modular monolith is sometimes described only as an intermediate stage before microservices. That is not a requirement.
If the system can be safely managed as one deployment, has independently understandable modules, explicit data boundaries, fits within one deployment model, and does not require independent team deployment, a modular monolith can remain a durable architecture.
A separate service becomes valuable when it solves a new requirement.
9.10.12. Stronger Signals for Extracting a Service
Useful reasons for moving a module behind a service boundary include independent scaling, independent deployment, physical fault isolation, a distinct security or trust boundary, a genuinely different runtime requirement, natural team ownership, or independent data and invariant ownership.
Weaker justifications include adopting microservices because they are modern, because the system might become large someday, or because every domain noun appears to deserve its own service.
9.10.13. Test Logical Independence Before Physical Distribution
A module can be tested for logical independence before it moves outside the process.
Package visibility, architecture tests, or module-verification tooling can protect module APIs and dependency direction.
In the Spring ecosystem, Spring Modulith can model application modules, their exposed interfaces, and inter-module dependencies and can verify structural constraints.
The important part is not the specific tool.
The durable engineering question is:
does the logical boundary work
before a network boundary is added?Moving a poor logical boundary into another process only makes the boundary more expensive.
9.10.14. Controlled Extraction Instead of a Broad Rewrite
Splitting a proven large application into services in one operation changes business behavior and deployment/failure topology at the same time.
A more controlled sequence is:
measure existing behavior
↓
identify the module boundary
↓
reduce internal coupling
↓
establish a contract
↓
move the boundary out of process only if justified
↓
verify equivalent required behaviorThis follows the same principle as maintenance engineering: modernization should solve a real problem through controlled change rather than recreate proven behavior for architectural fashion.
9.10.15. Final Principle
The useful question is not which architecture is more modern.
It is:
which boundary do we need to move
outside the process,
which concrete problem does that solve,
and which new failure and operational
costs are we accepting in return?A distributed architecture does not eliminate complexity. It moves complexity.
9.12. Assumptions, Evidence, and Reversibility in Architecture Decisions
A software architecture decision is not merely the name of a selected technology. An architecture decision record should state context, quality attribute, alternatives, binding constraints, and accepted trade-offs. A concurrency decision, for example, depends not only on thread count but also on database connections, memory usage, queue latency, recovery behavior, and observability. Recording why an alternative was rejected enables later engineers to reconstruct the original reasoning.
The decision needs testable success criteria. Instead of fast,'' define user-visible P95/P99 latency and completed throughput under a specified workload; instead of secure,'' define tests of authorization boundaries or acceptance criteria derived from a threat model. A numerical target is not in itself a guarantee: the measurement environment and workload must be described before different versions can be compared.
Reversibility also belongs in architecture analysis. Irreversible data migration, an immutable production schema, or noncompensatable external effects may increase change risk. Where changes can be isolated, a feature switch or a return path to previously verified behavior may be appropriate. A decision record is not immutable doctrine; it is a technical justification that can be revisited when evidence changes.
9.13. Contract and Lifecycle Design in Dataflow Components
Each component in a dataflow architecture declares an input schema, acceptable workload, determinism of its transformation, failure channel, and replay semantics. Platforms such as Apache NiFi distinguish construction, scheduling, stopping, and version migration from the processing algorithm itself. An undefined transaction boundary between processing and external side effects can produce duplicates after restart.
Architecture validation examines interface contracts independently of implementation code: input source, identity key, backpressure, failure classes, retry behavior, and idempotent target writes. An abstract component diagram alone is insufficient; retained behavior and data guarantees at each failure point must be explicit.
Unit 10: CASE, Toolchains and Reengineering
10.1. CASE Concept
CASE tools historically supported modeling, documentation, code generation and project management. The objective was consistency among engineering artifacts.
10.2. Tool Integration
Tools add value when information flows between them reliably. A diagramming tool that is never updated after implementation quickly becomes misleading.
10.3. Modern Engineering Toolchain
A contemporary toolchain may integrate:
issue/requirement tracking
↓
source control
↓
code review
↓
CI build + tests + static analysis
↓
artifact repository
↓
deployment automation
↓
monitoring / incident feedbackTraceability can be maintained through identifiers and immutable build metadata rather than manual duplication.
10.3.1. Trust Boundaries in AI-assisted Engineering Toolchains
AI-assisted code changes remain subject to the existing engineering chain of version control, dependency resolution, build, static analysis, testing, and deployment. Terminal access does not authorize an agent to bypass these stages or declare success without evidence. The development tool, execution permission, and verification mechanism are distinct responsibilities.
A reproducible check starts from a known revision, records dependency versions, runs the required checks in a controlled environment, and independently verifies expected output. As automated change volume increases, generated lines of code alone cannot measure engineering productivity. Review effort, defect remediation time, and rollback success are also outcomes of the delivery pipeline.
10.4. Selecting Tools
Tool choice should consider interoperability, automation, exportability, vendor lock-in, learning cost, security and whether the tool supports the actual engineering process.
10.5. Reverse Engineering and Reengineering
Reverse engineering recovers design, interfaces or behavior from an existing system. It is necessary when documentation is absent, for migration, compatibility, security analysis and legacy modernization.
Reengineering
Reengineering improves an existing system while preserving required external behavior. It can include refactoring, architecture changes, data migration, language/platform migration and replacement of obsolete components.
A safe reengineering process establishes behavioral baselines and migration tests before changing internals. Otherwise "modernization" can destroy undocumented but required behavior.
Unit 11: Bounded Architecture for Deterministic Analytical Systems
As an analytical application grows, architecture must control not only algorithms but also when computation happens, which data version it belongs to, and how it is separated from side effects.
11.1. Immutable Analytical Snapshots
Users may ask many questions over the same dataset. Re-normalizing raw data for every query is expensive and can create inconsistent state.
A versioned snapshot can contain:
snapshot
├─ normalized records
├─ indexes
├─ aggregates
├─ graph view
├─ time view
└─ versionNew data creates a new version instead of mutating the old one.
11.2. Lazy Computation and Memoization
Not every expensive analytical view must be computed before it is needed.
first request
-> compute
-> cache inside snapshot
later request on same snapshot
-> reuseMemoization is safe only when the function is pure or its input version is explicit.
11.3. Bounded State
Conversation and interaction history should not grow without limit.
Useful bounds can apply to the last N messages, last M structured results, current selection, active period, and pending clarification.
This reduces memory use and contamination from stale context.
11.4. Result Frames
An analytical result can be stored as structured state rather than only HTML or prose:
resultState
├─ analysisVersion
├─ operation
├─ period
├─ items[]
├─ evidenceRefs[]
└─ bindings[]This provides a stable contract between analytics and UI.
11.5. Query Plans
A natural-language or UI request does not need to call data access directly.
request
↓
QueryPlan
↓
validator
↓
executorThe plan can represent operation, metric, period, limit, sort order, and dependent steps.
11.6. Declarative Capability Catalogs
As query planning grows, duplicating operation preconditions across the router, planner, executor, and UI creates policy drift. A bounded analytical system can instead describe supported operations in a declarative capability catalog.
A catalog entry may specify:
- required input or source types,
- mandatory parameters and preconditions,
- approximate cost class,
- whether heavy work requires a worker or separate execution path,
- whether scope constraints such as a date range are supported,
- whether partial data is acceptable,
- output kind.
The planner emits only operations present in the catalog, and the executor validates the same contract before execution. An unsupported operation therefore becomes a fail-closed decision rather than a runtime surprise.
This is a small form of policy as data. Once policy is represented explicitly, it can be versioned, tested, and reviewed as part of change impact. Adding a capability means extending one contract instead of distributing the same rule across multiple conditional branches.
A capability catalog is not authorization. Architectural support for an operation does not imply that a particular user or data scope may execute it; authorization and resource scope remain independent execution-time checks.
11.7. Bounded fan-out
Composite requests can explode computationally.
A planner can enforce budgets for dependent steps, fan-out, result count, and dialogue depth. These limits improve predictability and contain failures as well as performance cost.
11.8. Side-effect Isolation
If an analytical function is defined to read data and produce derived results, it should not modify source observations:
analysis(sourceSnapshot)
-> new derived resultSide effects belong in a separate service or tool layer.
11.9. Deterministic Tie Breaking
Random ordering among tied scores creates unstable UI and brittle tests.
A stable order can use:
score desc
secondary metric desc
stable id asc11.10. Non-mutating Validators
A validator should report invalid state rather than silently rewrite it.
11.11. Safe Fallback
When a richer component fails, the system can degrade to a less capable but trustworthy mode:
rich renderer fails
-> simple verifiable renderer
advanced analysis is not evaluable
-> basic summary + limitation
specialized query is unclear
-> supported operation listBounded deterministic architecture is a discipline of explicit version, state, computation, plan, cache, and side-effect boundaries.
11.12. Reading the Change Loop from Requirements Through Operation
The course forms a feedback system rather than a one-way sequence:
requirements
↓
design / architecture
↓
implementation
↓
verification
↓
operation
↓
observed failures and new needs
└──────────────→ requirements/designProject management controls resources and risk around this loop. Configuration management preserves reproducibility. Quality and testing supply evidence. Maintenance returns operational knowledge to the next design decision.
The central engineering lesson is that software quality is an emergent property of the complete lifecycle. Correct source code cannot compensate for an incorrect requirement, an unsafe deployment, an uncontrolled data migration or an architecture whose failure modes were never designed.
11.13. Separate Deep Dives for Security and Change
This course keeps process, requirements, design and quality in the general software-engineering frame. For threat modeling, trust boundaries, secure development and verification, see Secure Software Engineering. For dependency structure and change resilience, see Change-Resilient Architecture with SOLID.
11.14. Change Engineering in Proven Large Systems
Designing a new system and changing a system that has worked for years are different engineering problems. Greenfield development can emphasize architectural ideals; in a proven production system, observed behavior, user habits, data reality, and operational procedures are also part of the contract.
The first question in modernization is therefore not “how would I design this from scratch today?” but “which behavior must be preserved, why, and which real problem am I solving?” Rebuilding a working system merely to use a newer framework, shorter code, or a more symmetrical package structure can increase change risk without improving the operational outcome.
Safe change follows a few durable principles:
verify current behavior from source
bound the affected path
make the smallest coherent change
preserve backward compatibility
verify according to risk
harvest durable new rules into project context“Old” and “wrong” must remain separate concepts. A schema limitation, historical duplicate data, a familiar keyboard behavior, or an established integration path may differ from an idealized design, but once it becomes a production contract, changing it requires a separate requirement and verification effort. A proven defect, on the other hand, should not be preserved merely because it is legacy.
Staged modernization can therefore be stronger than a large rewrite. Observable and recoverable boundaries are selected first, behavioral equivalence is demonstrated, and only then does the next stage begin. Each stage has its own acceptance criteria and residual risk. From the perspective of Software Test Engineering, evidence is produced at the same cadence as the change.
The interface is part of this contract as well. In a heavily used business application, panel ordering, keyboard shortcuts, and record-selection behavior are more than visual preferences; they can become muscle memory. Such changes should be treated as UX regressions under UI/UX Engineering in Web Applications.
The goal of maintenance engineering is not to erase every historical trace. It is to make the reasons for behavior visible, remove unnecessary complexity, and keep the impact surface of each new change small. This connects process engineering directly with Clean Code and Maintainability Engineering.
11.14.1. Related Technical Sources
- Spring Modulith Reference: https://docs.spring.io/spring-modulith/reference/
- Martin Fowler, Microservices: https://martinfowler.com/articles/microservices.html
- Martin Fowler, MonolithFirst: https://martinfowler.com/bliki/MonolithFirst.html
- Sam Newman, Building Microservices, 2nd ed., O'Reilly Media, 2021
11.14.2. Behavioral Inventory and the Evidence Chain of a Change
Before modifying a critical system, the running behavior should be inventoried alongside written requirements. Source code, user workflow, data reality, and operating rules may have drifted apart over time. Relying on documentation alone or on source alone can therefore produce incomplete evidence.
A practical change chain can be expressed as follows:
observed behavior
|
v
invariant to preserve
|
v
change boundary
|
v
verification evidence
|
v
new durable ruleEvery change should separate three sets: behavior that must remain unchanged, behavior that is intentionally being changed, and behavior that is still uncertain. The uncertain set should be reduced before implementation; otherwise the developer can redefine an existing contract accidentally.
The change boundary is not merely a file list. A data-write path can affect other views of the same entity; a client-lifecycle change can alter automatic refresh and keyboard focus; a concurrency limit can change both latency and failure behavior. Impact analysis therefore needs to follow call paths, data ownership, and user tasks.
Evidence should be proportional to risk. A local visual adjustment may need only a narrow smoke check. A change in data integrity, authorization, or concurrency may require compilation, contract verification, negative scenarios, and an end-to-end workflow together. Software Test Engineering develops these evidence layers further.
The final step is to move only genuinely durable information into project context. Turning every implementation detail into a permanent rule bloats the context, while failing to record a high-impact invariant forces the same investigation to be repeated. Documentation then becomes more than a post-change summary: it narrows the decision surface of the next change.
11.15. Software Development Life Cycle
The Software Development Life Cycle (SDLC) organizes activities from requirements and planning through design, implementation, verification, deployment, operation, and maintenance. Waterfall, incremental, iterative, agile, and DevOps-oriented processes arrange these activities with different sequencing, feedback frequency, and delivery cadence. SDLC is therefore not synonymous with one process model; it is the broader lifecycle through which a need becomes an operable and maintainable software system.
11.16. Traceability from Requirement to Deployment
A durable software-engineering problem, independent of process methodology, is being able to explain why a change exists and what technical evidence supports it. Traceable links among requirements, design decisions, code changes, tests, and releases are especially valuable in critical systems and post-incident analysis.
This does not require a heavyweight document chain in every project. Existing engineering artifacts such as issue IDs, architecture decision records (ADRs), commits, test results, and release records can provide sufficient traceability when linked consistently.
11.16.1. The Proper Role of Design Patterns
Patterns such as Factory, Strategy, Observer, Adapter, Facade, and Singleton give names to recurring design problems. Using a pattern is not itself evidence of quality. Introducing a pattern before the problem exists can increase class count, indirection, and cognitive load.
Strategy can make a changing algorithm selectable behind a contract; Adapter can translate incompatible interfaces; Observer can model publication to multiple subscribers. Singleton simplifies global access but can increase hidden coupling and test-isolation cost. Patterns should be selected for an explicit trade-off.
11.16.2. Git and Version Control
A commit in distributed version control is more than a file backup; it is a unit of change history. Small, meaningful, buildable commits reduce review and rollback cost. Branching strategy should match the team's release model; there is no universal branching scheme.
A merge conflict does not necessarily mean either developer worked incorrectly. It means the same region evolved on different histories. Correct resolution must preserve business semantics rather than merely produce syntactically compilable text.
Git and Apache Subversion (SVN) solve version control with different repository models. A Git working clone contains repository history and supports local commits in a distributed model, whereas SVN traditionally uses a central repository with working copies. That distinction affects offline work, branching and merging, access control, and strategies for large binary assets. Tool selection should follow repository size, team workflow, authorization, and integration requirements rather than a simple “newer is better” rule.
11.16.3. CI/CD and Automation
Continuous Integration emphasizes frequent integration with automated verification. Continuous Delivery keeps software in a deployable state; Continuous Deployment automatically releases successful changes to production. Delivery and deployment are not synonyms.
A pipeline containing many tools is not a quality guarantee. Fast reliable feedback, deterministic builds, necessary tests, security controls, and controlled artifact production matter more than tool count.
11.16.4. Monoliths, Modular Monoliths, and Microservices
A monolith is not inherently bad architecture, and a microservice is not inherently good architecture. Microservices can enable independent deployment and scaling, but introduce network failure, distributed consistency, observability, versioning, and operational overhead. A well-bounded modular monolith can be the lower-complexity solution for many systems.
Service boundaries should be justified through domain boundaries, team ownership, independent change/scale needs, and data ownership rather than technology fashion.
11.16.5. Bounding Change Risk
In a proven production system, refactoring should not rewrite behavior merely to make code look cleaner. Characterize current behavior, narrow the change boundary, identify dependent contracts, and preserve rollback. For critical paths, small, observable, reversible changes generally carry less operational risk than a broad one-shot rewrite.
11.16.6. Traceable Evidence for AI-generated Changes
An agent-generated patch should link to a requirement identifier and explicitly document affected components, acceptance criteria, and executed tests. The evidence chain follows requirement → design decision → change set → verification → release. Model identity and prompt version are useful provenance metadata, not substitutes for correctness.
During review, the rationale for a proposed change must be separated from the behavior it modifies. Unrelated reformatting or dependency upgrades concealed in the same patch complicate rollback and root-cause analysis. The objective is to produce small auditable changes while keeping acceptance boundaries visible for every modification.
11.17. Architecture Decisions and Change Risk
Architecture is a history of decisions, not only a diagram. When rationale disappears, teams repeat old debates or reverse constraints they no longer understand. A lightweight Architecture Decision Record can preserve context, alternatives, the chosen option, expected consequences, and conditions for reconsideration.
Change risk depends more on dependency direction than on line count. A small change to a shared contract can affect many consumers, while a large isolated change may remain local. Review and testing should therefore focus on blast radius rather than diff size alone.
11.18. Keeping Engineering Decisions Traceable
A requirement becomes more useful when it is connected to the user need it serves and to evidence that verifies it. Traceability from requirement to architecture decision, implementation change, and test result makes change impact easier to assess.
Architecture decision records should capture context, alternatives, the selected approach, and conditions for revisiting the decision. They do not declare one architecture universally correct; they preserve the assumptions behind the choice.
Quality metrics are contextual. Coverage, defect counts, and lead time do not prove quality in isolation. The decision supported by a metric and the behavior it may incentivize should remain explicit.
11.19. Software Engineering for Learning Systems
In an AI-enabled system, the model is only one architectural component. Requirements, data, model artifacts, runtimes, service boundaries, and human validation must be engineered together. The connection between software engineering and AI therefore appears primarily in managing behavior and change, not in the model-training algorithm itself.
In conventional software, a large part of behavior is determined by source code and configuration. In a learning system, data and learned parameters also enter the state:
output = f(code, configuration, data, model, runtime)This broadens versioning. A source commit alone may be insufficient to reproduce a result; the dataset slice, label version, feature pipeline, model artifact, runtime, and evaluation set may also need stable identities.
Requirements must also be more explicit than “achieve high accuracy.” Relevant operating distribution, false-positive and false-negative costs, latency limit, memory budget, fallback behavior, and mandatory human-review conditions should be stated separately. Without those constraints, a model metric cannot be traced to a system requirement.
Architecture should keep model interaction behind an explicit contract rather than scattering model calls throughout the codebase. Input validation, timeout policy, error handling, and model version can then be controlled at one boundary. If inference is remote or out-of-process, network failure, capacity exhaustion, and partial failure become ordinary system states.
business rule
↓
model contract
↓
model / inference runtime
↓
measured output
↓
application decisionA model score and a business decision need not be identical. The application may combine the score with thresholds, authorization, or contextual rules. This separation improves testability and change control.
The data path is part of the software life cycle as well. Divergence between training preprocessing and production preprocessing creates training-serving skew. Missing-value policy, scaling, encoding, and feature semantics must therefore be versioned and tested as executable contracts.
A model update should be treated as an independent release event. The API may remain unchanged while the error distribution changes. Comparative evaluation, shadow or canary deployment, and a rollback path can be necessary. Rollback may need to restore the model and its associated preprocessing contract together.
AI-assisted code generation does not remove these responsibilities. Generated code still needs requirements, dependency and license review, security analysis, and regression validation. “The model suggested it” is not an engineering rationale.
The underlying principle remains familiar: make every behavior-defining input visible, define boundaries explicitly, trace change, and validate the result against requirements. A learned model introduces a new class of artifact; it does not replace software-engineering discipline.
11.20. AI Engineering: Turning a Probabilistic Component into a Software System
AI engineering is not the same as using a chat model to help write code. Turning a model into a reliable production component requires its own lifecycle.
Problem
|
Data / Context
|
Model + Prompt + Tools
|
Evaluation
|
Serving
|
Observation
|
Feedback / RegressionA successful API call does not mean the feature is production-ready.
11.21. Testing Probabilistic Components
Traditional functions often support exact deterministic assertions.
LLM components may instead require contracts around required fields, forbidden claims, citation use, schema validity, and minimum task quality.
A stable evaluation set should contain normal examples, boundary cases, historical failures, adversarial inputs, and no-answer cases.
feature change
|
unit/integration tests
+
AI evaluation suite
|
release gateAI evaluations complement rather than replace conventional software tests.
11.22. Version Model, Prompt, Data, and Tools Together
The same code can behave differently after a model, prompt, embedding, or retrieval change.
AI_RELEASE =
model
+ tokenizer
+ prompt
+ embedding
+ retrieval config
+ tool schema
+ evaluation datasetProduction provenance should make it possible to identify which model, prompt, retrieval version, and tool output produced a result.
11.23. Testing Non-Deterministic Output
With sampling enabled, exact string equality can be the wrong assertion. Tests can instead check structural invariants and task-level rates:
is JSON valid?
are required sources present?
was a forbidden field generated?
what is success over 100 cases?Deterministic settings remain useful when they are compatible with the product because they simplify regression analysis.
11.24. Canary, Shadow, and Progressive Delivery
A new model should not automatically receive all traffic.
Shadow deployment observes real inputs without changing user-visible decisions.
Canary deployment sends a small traffic share to the new version.
Measure quality, tail latency, error rate, compute cost, abstention rate, and safety regressions.
11.25. Rollback Is More Than Reverting a Model File
Behavior can change because of prompts, embeddings, retrieval parameters, tools, or policy. A rollback should restore a compatible bundle of these artifacts.
11.26. AI Engineering vs AI-Assisted Development
AI-assisted software development uses AI to help produce code, tests, documentation, or analysis.
AI engineering designs models, data, evaluations, serving, security, observability, and lifecycle as a production capability.
coding with AI
!=
engineering an AI systemIntent-Driven Programming develops specification and verification at the developer-agent boundary, while Large Language Models and RAG cover model and retrieval layers.
11.26.1. Code Generation Speed and Lifecycle Cost
Faster code generation by an agent does not by itself demonstrate faster reliable delivery. Total lead time includes requirements analysis, generation, review, testing, integration, deployment, and possible rollback. As generation accelerates, review and test capacity may become the new bottleneck.
Comparisons should hold requirements and acceptance criteria constant and measure end-to-end results for human-assisted and agent-assisted alternatives. Relevant measures include effort per verified change, escaped defects, change failure rate, and maintenance burden. Agent involvement does not remove engineering accountability or ownership of the final change.
11.27. AI Technical Debt
The concept of hidden technical debt in machine-learning systems becomes broader in generative-AI products.
Sources include model-provider coupling, prompt accumulation, untested data changes, retrieval-index drift, tool-schema incompatibility, and unobserved quality regression.
A one-line model configuration change can therefore represent a large system change. Software-engineering principles still apply: boundaries, dependencies, tests, provenance, and rollback must remain explicit.
11.28. Cognitive Requirements and Adaptive Operation
Functional requirements state what a system does, while quality requirements constrain how it does it. Complex systems that operate jointly with people often need a third layer: cognitive requirements. These specify what information practitioners must perceive in context, which uncertainty must remain distinguishable, which relationships must be understood, and how the result of an action can be confirmed.
“The system shall display an error” may be implementable yet weak as decision support. A stronger requirement can state that the practitioner must be able to see which function is affected, how fresh the data is, which primary signals confirm the condition, and which safe alternative actions remain available in the same work context.
This connects software requirements to Cognitive Systems Engineering. Cognitive Task Analysis can expose tacit cues and expertise inside difficult decisions, while Cognitive Work Analysis models purposes, constraints, and alternative strategies that can remain valid beyond the current procedure.
11.28.1. Work as Imagined and Work as Done
Requirements and process documents usually describe an intended form of work. Production conditions vary: time, capacity, data quality, staffing, connectivity, and dependencies can all change. Resilience engineering treats the resulting adaptations not merely as procedural deviations but as part of the system's everyday adaptive capacity.
Critical functions should therefore specify degraded operation and recovery as well as the nominal path:
normal operation
→ resource pressure or partial failure
→ restrict safe secondary capabilities
→ preserve core service
→ preserve state visibility
→ controlled recoveryThe engineering question is not limited to whether the system is “up” or “down”. Requirements should identify which capabilities remain available under which conditions, when loss of data or automation requires human takeover, what constitutes a minimum safe service level, and how those transitions will be verified.
11.29. Architectural Characteristics, Fitness Functions, and Engineering Architecture Decisions
Software architecture is not merely a choice between layers and microservices. The deeper problem is to identify which quality properties the system must preserve and how those properties will be verified as the system evolves.
11.29.1. Derive Architectural Characteristics from Requirements
A functional requirement may say:
The user shall generate a report.
Architectural characteristics define the conditions under which that function remains acceptable:
report
├─ begins within an agreed latency budget
├─ never exposes unauthorized data
├─ remains available under peak load
├─ degrades safely when a dependency fails
└─ can evolve without unnecessary service disruptionPerformance, security, availability, scalability, observability, maintainability, deployability, and fault tolerance cannot all be maximized simultaneously.
Architecture is therefore a trade-off optimization problem.
11.29.2. Make Quality Goals Testable
Terms such as “high performance,” “scalable,” or “maintainable” are too vague to govern a design.
A stronger form combines:
characteristic
+
scenario
+
measure
+
acceptance boundaryFor example, a deployability characteristic can describe a change isolated to one module and require that unrelated modules need not be redeployed.
This turns a quality aspiration into an engineering contract.
11.29.3. Architecture Fitness Functions
Once a decision is made, the harder question is whether the codebase still preserves that decision after years of change.
A fitness function is a mechanism that repeatedly evaluates an architectural property.
It can be:
automated test
static architecture rule
performance budget
security scan
dependency-graph rule
operational monitor
failure/chaos experiment
manual architecture reviewExamples include a CI rule that prevents domain code from depending on the web layer, or a performance check that protects a critical p99 latency budget.
Architecture then becomes continuously verified behavior rather than a static diagram.
11.29.4. Avoid Turning a Metric into a Bad Target
Service count, class count, test coverage, or average response time do not prove architectural health by themselves.
Metrics create incentives. A target of “small services” can produce meaningless fragmentation; a target of 100% coverage can produce low-value tests.
A useful fitness function should directly represent desired behavior, avoid obvious perverse incentives, remain interpretable, and preferably be observable as a trend.
11.29.5. From Coupling to Connascence
Traditional coupling counts can miss runtime dependency.
Connascence asks:
If one component changes, what else must change for the system to remain correct?
A static example is two modules that depend on the exact representation of a shared type.
A dynamic example is a synchronous service dependency:
service A
↓ synchronous call
service BIf B slows down, A's request path inherits that behavior.
Coupling is therefore not only import count. It is also the extent to which components share change fate and runtime fate.
11.29.6. Component Granularity: Smaller Is Not Automatically Better
Components that are too coarse can accumulate internal coupling and enlarge deployment/test scope.
Components that are too fine can multiply calls, protocols, failure modes, version coordination, and operational overhead.
Granularity should consider:
domain cohesion
rate of change
team ownership
data ownership
deployment need
runtime couplingA table-per-service or entity-per-component strategy often models storage rather than workflow. The data model informs architecture; it does not determine architecture by itself.
11.29.7. Architecture Quantum
In distributed systems, independent deployment does not always mean genuine operational independence.
A component may still require another service, a shared database schema, a synchronous call chain, or coordinated release timing.
The architecture quantum is a useful analytical concept for thinking about independent deployability together with functional cohesion and runtime coupling.
The goal is not to maximize the number of services. It is to understand the smallest operational unit that actually has to change and operate together.
11.29.8. Fallacies of Distributed Computing
Remote boundaries make several assumptions dangerous:
the network is always available
latency is negligible
bandwidth is infinite
the network is secure
topology does not change
there is one administrator
transport is free
the network is homogeneousMoving a method call across a network introduces serialization, transport, remote queues, timeouts, retries, partial failure, and version compatibility.
A microservice decision is therefore also a failure-model and operations decision.
11.29.9. Architecture Styles Are problem-solving Tools
Layered architecture can provide a clear general-purpose structure but does not automatically solve plugin extensibility or rapidly changing workflows.
Pipeline architecture decomposes transformation into sequential stages.
Microkernel architecture separates a stable core from extensions.
Service-based architecture uses coarse-grained domain services and can introduce less distribution than fine-grained microservices.
Event-driven architecture decouples producers and consumers in time, at the cost of ordering, delivery, and observability complexity.
Space-based architecture can reduce centralized data bottlenecks under extreme concurrent load by distributing state and processing, while increasing consistency and operational complexity.
Choose a style for its dominant architectural characteristics, not its popularity.
11.29.10. ADRs Preserve Why a Decision Was Made
An Architecture Decision Record should contain more than “use technology X.”
A useful structure records context, the decision, alternatives, consequences, and the conditions under which the decision should be revisited.
It does not claim eternal correctness. It preserves why the decision was reasonable under a particular set of constraints.
11.29.11. Risk Matrices and Risk Storming
Architectural risks differ in severity. A simple framework can reason about:
risk = impact × likelihoodand use that to prioritize discussion.
A team can then examine availability, data integrity, security, performance, scalability, and changeability risks on a shared architecture model.
Risk storming is valuable because different roles often see different failure modes. The output should evolve as the architecture, traffic profile, and dependencies change.
11.29.12. Practical architecture-decision Sequence
Ask:
1. Which business problem is being solved?
2. Which architectural characteristics dominate?
3. What options exist?
4. What trade-offs does each option introduce?
5. Which new coupling appears?
6. How do data and deployment ownership change?
7. What is the largest failure mode?
8. How will the decision be verified?
9. Under what condition should the decision be reopened?Architectural maturity is not the number of patterns or fashionable technologies in a system. It is the ability to make constraints visible, choose trade-offs explicitly, and continuously verify that decisions still hold.
Unit 12: Deepening Modeling Semantics with UML 2.5.1
The value of UML is not the existence of a diagram; it is the semantics the model makes explicit. OMG's current formal UML version is 2.5.1. Older exam material based on UML 2.0/2.1.x still contains many durable concepts, but the version framing should be updated.
12.1. Association, Aggregation, Composition, Generalization, and Realization
Association states a structural relationship between classifier roles. It does not automatically imply ownership or lifecycle.
Shared aggregation expresses a weaker whole-part relation; in many code designs its additional meaning is limited and it should not be added mechanically.
Composition expresses strong whole-part lifecycle ownership. A field reference alone is not sufficient to establish composition.
Generalization models an is-a specialization relation.
Realization indicates that a classifier implements a specification or interface contract.
Arrow choice therefore represents a modeling claim rather than decoration.
12.2. Multiplicity and Navigability
Multiplicity values such as:
1
0..1
*
1..*
0..*represent domain constraints.
For example:
Order 1 ----- 1..* OrderLinestates that an order has at least one line. The rule can then be traced into implementation, persistence, and tests.
Navigability expresses a modeled direction of access; an association does not automatically require two runtime references.
12.3. Include versus Extend in Use Cases
include and extend are not interchangeable reuse arrows.
include extracts mandatory shared behavior used by a base use case.
extend adds conditional or optional behavior at defined extension points.
Turning every UI screen or function into a use case generally weakens the model.
12.4. Time and Responsibility in Sequence Diagrams
Useful sequence-diagram elements include lifelines, messages, execution specifications, returns, creation, and destruction.
Combined fragments describe structured interaction:
alt -> alternatives
opt -> one conditional region
loop -> repetition
par -> parallel interaction regionsA par fragment describes interaction semantics; it does not prove the implementation is thread-safe.
12.5. State Machine versus Activity
A state machine models an entity's lifecycle and event-driven transitions.
An activity diagram models work/control flow, decisions, and fork/join behavior.
A workflow is often better expressed as an activity, while an entity lifecycle such as CREATED -> PAID -> SHIPPED is naturally state-oriented.
12.6. Other Useful Diagram Families
Object diagrams show instance snapshots.
Communication diagrams emphasize links and numbered interactions.
Component diagrams show coarse software components and provided/required interfaces.
Deployment diagrams map artifacts to nodes and execution environments.
Timing diagrams emphasize state/value changes over time.
Composite-structure diagrams expose internal parts, ports, and connectors.
Profiles adapt UML to a domain with controlled stereotypes and tagged values.
A project does not need every UML diagram.
12.7. Model-code-test Traceability
A useful model can participate in a traceable chain:
use case / requirement
↓
component responsibility
↓
sequence interaction
↓
API / class implementation
↓
testWhen code changes but the model does not, diagram debt accumulates. Detail should be maintained only where the model continues to reduce ambiguity.
12.8. Modeling Principle
The goal is not to maximize boxes and arrows.
Ask:
Which uncertainty does this model reduce better than prose or source code?
That question turns UML from exam notation into an engineering communication tool.
12.9. Tracing Behavioral Invariants from Requirements to Regression Tests
In critical software, a requirement expressed only as a screen layout or endpoint response is incomplete. A behavioral invariant specifies a property preserved across all permitted state transitions. For example, an action must refer to its intended entity, one physical command must not create duplicate persistent mutations, and unauthorized contexts must not update data. These rules should be traceable across requirement, design choice, execution boundary and test case.
A small code change may have a large behavioral footprint. Moving keyboard event handling into the capture phase can create new interactions with modal dialogs and text controls that previously handled the event. Impact analysis therefore covers event routes, workspace changes and asynchronous completions, not only changed lines.
The test hierarchy extends from pure-function validation to API contracts, browser event dispatch and full user scenarios. Each layer detects different failures; a UI screenshot cannot establish data-integrity invariants by itself.
12.10. Acceptance, Rollback, and Compliance in Bounded Changes
Acceptance of a maintenance change requires both the new capability and preservation of contracts that are explicitly out of scope. The baseline version, modified files, dependent components, unchanged persistence schema and rollback path should be recorded. An observed contract violation cannot be treated as a successful change simply because the new feature works; it triggers a risk-based repair or rollback decision.
Measurable acceptance criteria may include correct command routing across a focus matrix, no stale-state restoration by asynchronous work, and p95 latency staying within an agreed budget under matched load. Performance gains do not compensate for loss of correctness. Preserving behavior means honoring failure-path contracts as well as the happy path.
References
- Apache Software Foundation. Subversion Documentation. https://subversion.apache.org/docs/
- D. Sculley et al. “Hidden Technical Debt in Machine Learning Systems.” Advances in Neural Information Processing Systems, 28, 2015.
- Eric, Mihail. The Modern Software Developer (CS146S). Stanford University, course materials, 2025–2026. https://themodernsoftware.dev/
- Git Project. Git Documentation. https://git-scm.com/docs
- Heather Adkins; Betsy Beyer; Paul Blankinship; Piotr Lewandowski; Ana Oprea; Adam Stubblefield. Building Secure & Reliable Systems: Best Practices for Designing, Implementing, and Maintaining Systems. O’Reilly Media, 2020.
- Ian Sommerville. Software Engineering. Addison-Wesley, 2010.
- IEEE Computer Society. Guide to the Software Engineering Body of Knowledge, Version 3.0. IEEE Computer Society, 2014.
- ISO/IEC. ISO/IEC 25010:2011 Systems and software quality models. International Organization for Standardization, 2011.
- ISO/IEC/IEEE. ISO/IEC/IEEE 29148:2011 Requirements engineering. ISO / IEC / IEEE, 2011.
- Kent Beck; Cynthia Andres. Extreme Programming Explained: Embrace Change. 2nd ed. Addison-Wesley, 2004.
- Mark Richards; Neal Ford. Fundamentals of Software Architecture: An Engineering Approach. O'Reilly Media, 2020.
- Michael T. Nygard. Release It!: Design and Deploy Production-Ready Software. 2nd ed. Pragmatic Bookshelf, 2018.
- Nancy G. Leveson. Engineering a Safer World: Systems Thinking Applied to Safety. MIT Press, 2011.
- National Institute of Standards and Technology. Artificial Intelligence Risk Management Framework: Generative Artificial Intelligence Profile (NIST AI 600-1). 2024. DOI: https://doi.org/10.6028/NIST.AI.600-1
- NIST. Secure Software Development Framework (SSDF), SP 800-218. 2022. https://csrc.nist.gov/pubs/sp/800/218/final
- Object Management Group (OMG). OMG Unified Modeling Language (UML), Version 2.5.1. 2017. https://www.omg.org/spec/UML/2.5.1/