Systems Design and Analysis
Turning vague problems into clear software solutions.
Welcome to Systems Design & Analysis! In this course, you'll learn how the software industry works, how to effectively design and analyze programs, and how to effectively communicate with others when working on software projects.
In this course, you'll get to experience the full journey of building a software system, from figuring out what a client actually needs, to sketching out models and diagrams, to designing how all the pieces fit together. You'll step into roles like business client, systems analyst, and software designer to see projects from different perspectives. Along the way, you'll explore popular approaches like Agile and Waterfall, practice writing use cases, and work with diagrams that map out real systems. By the end, you'll know how to turn a vague idea into a clear design, and you'll have skills you can carry into your future career.
I got drawn to programming because the logical thinking and problem-solving really work well for my brain, and I find it incredibly satisfying to finally crack a sneaky bug. I hope to share that same problem-solving mindset with you throughout the course.
Course Description (Course Outline)
In this course, students participate in the key phases in the Software Development Life Cycle (SDLC) of a small-scale software system. A variety of software development methodologies (e.g. Waterfall Method, Unified Process (UP), and Agile) are explored. During this course, students take on roles such as business clients, systems analysts, and software designers. In these roles, students identify and document business requirements, develop software models and create integrated system designs.
Systems Analysis
Systems Analysis is about understanding the problem before building a solution.
When developing software, it's tempting to jump in and start building things straight away. When writing small programs like a calculator, simple game, or basic script to automate a task, you've probably done just that! Opened an editor, started typing, and figured things out as you went along.
In general that works quite well and is very efficient when:
- the program is small
- only you are using it
- the "requirements" live in your own head
But when software grows, with mroe features, data, users, or developers working on it, the cost associated with just jumping in starts to go way up. If you don't understand the problem you're solving, you risk building the wrong thing, creating confusion, or missing requirements entirely.
Systems Analysis gives us a toolkit to slow down just enough to ask the right questions about what the system should do, capture the answer to those questions, and make sure everyone working on the problem understands the problem the same way.
Think of housing: if we're building a house for a client and decide on a nice, space-efficient 2-bedroom apartment but then discover on moving-day we have a family of 12 moving in, we can have the best apartment in the world but we've fundamentally not solved the problem.
Suppose a friend knows you're a developer, and they ask how to build a web-scraping bot that can navigate a site, follow links, search for keywords, and download files. Based on that request, you might reach for a library like Scrapy in Python and start coding.
A few days later, when you ask which site they want scraped, your friend explains they just need a CSV of updated pricing from a local store. Looking at the site, you realize it has an API - so a single request could have downloaded the CSV in five minutes.
This is the XY Problem: you solved for "Y," but what was really needed was "X." Systems Analysis helps us uncover X before we waste time on Y.
Target Canada
Target is the seventh-largest retailer in the United States, and has 1,989 stores across the country1. Many U.S.-based companies find success tapping into the Canadian market, as many of our spending habits are similar, and culturally we borrow a lot from the U.S. As of July 2025, Costco has 626 warehouses in the U.S., and 110 in Canada2 - which means Canada actually has almost twice as many Costco locations per capita as the country where it originated! Given that context, Target's Canadian expansion in 2013 looked like a safe bet.
However some of you may remember the brief time Target was in Canada. The experience was lacklustre, and despite a lot of initial excitement, the brand closed all 133 stores they had opened in fewer than two years of operation.
The Target Canada expansion has already been studied and will likely be talked about for a long time in Business classes. The full details of the failure go beyond the scope of this course, but if you'd like to read more you can see an article from Harvard Business Review, and there's a blog by Henrico Dolfing which contains a good summary of events.
The Problems
Target Canada's troubles were multi-faceted, touching everything from supply chains to customer expectations. At the heart of it all was an overly aggressive expansion strategy: instead of piloting a handful of stores and scaling gradually, Target tried to open more than 100 locations across the country in less than two years. This left little time to adapt U.S. practices to the Canadian market, test systems thoroughly, or resolve early warning signs before they cascaded into larger failures.
Software Problems, Too!
Target's problems went beyond business decisions and supply chain problems. There were serious software and data problems, too. Canada was Target's first attempt at international expansion, and they over-relied on our similarities and assumed there would be less internationalization headaches than there were. As part of the rollout, Target developed new software to handle the changes, but this software was riddled with probelms.
Bad Product Data
The inventory system was often missing data or wrong entirely, resulting in warehouses shipping the wrong quantities of goods, shelves sitting entirely empty of some goods, and back rooms overflowing with others.
Rushed Systems Integration / Rollout
Target pushed quickly for rapid expansion, so they integrated with all of their suppliers, warehouses, and stores at once. Early bugs weren't caught until they had already been scaled across the country.
Slow Feedback Loops
Store managers and employees were aware of problems immediately, but the system wasn't designed to capture that information quickly.
All of this lead to a series of cascading problems: Target couldn't achieve their core objectives anymore, and so between January 2015 and April 2015, they closed all 133 of their locations.
What requirements should target have validated first regarding the software requirements differences between the U.S. and Canada?
Software Development Lifecycle
The Software Development Lifecycle (SDLC) is the process used to deliver software with a predictable quality and cost.
Across Software Development, there are many different ways to organize work. Some methods emphasize speed and iteration, while others emphasize stability. The SDLC represents the general flow from ideation to completion of a piece of software. This may not always be linear, since some methodologies go back and forth between phases.
Phases
The SDLC is broken up into several phases. Depending on the context, source, and methodology you'll often see a number between 5-7 phases. This is due to the fact that with different frameworks and methodologies, you can have fewer or different phases. For example, when using Test-Driven-Development, the "Development" phase often includes testing first, then development, then validation. For the purpose of this course, we have broken the SDLC into a five-phase model to balance simplicity with completeness.
1. Requirements Gathering and Analysis: Understanding what the stakeholders actually need, and defining the goals of the system. This can involve interviews, workshops, documentation, and establishes functional and non-functional requirements.
2. Systems Design: Translating requirements into a "blueprint" for development. This phase produces technical designs such as architecture diagrams, database schemas, and other mockups.
3. Development, Validation, Testing: This is where the code is actually written, the features are actually built, and the work is validated to ensure it's working as expected. This phase is the most cyclical of all the phases, and often includes iterative testing alongside development.
4. Deployment: Delivering the software into a live environment where it can be used by real users.
5. Maintenance: The ongoing process of resolving bugs, adding new features, and ensuring the system continues to meet user needs as requirements evolve over time.
A common misconception is that the phases of the SDLC happen only once, in strict sequence. In reality, most software goes through many iterations of the lifecycle. Each new feature, bug fix, or release re-enters the cycle, often looping back to earlier phases. This is true even when following a Waterfall methodology, where iteration can happen through re-work loops or maintenance cycles.
Think of building a house. The first time through, you gather requirements (what kind of house?), design blueprints, build, inspect, and move in. Later, you might add a deck, remodel the kitchen, or replace the roof. Each of those improvements goes through its own mini-cycle of requirements → design → build → test → use. The house is never truly "finished", it's just continuously maintained and improved.
Requirements Gathering and Analysis
Requirements gathering and analysis is the first phase of the Software Development Lifecycle (SDLC). The goal is to understand what the software should do and why it is being built. In this phase, we identify the needs of stakeholders, clarify the scope, and document requirements to guide later phases.
Good requirements gathering reduces wasted effort, lowers costs, and helps ensure that the final product solves the right problem. Poor requirements, on the other hand, are one of the most common reasons software projects fail.
Throughout the SDLC we will regularly need to gather requirements for different parts of the project. There are several key activities associated with the Requirements Gathering and Analysis phase. These key activities are:
Identify Stakeholders: We must identify who will use the system, and how our system will benefit them. These are our Users.
Elicit Requirements: We must elicit requirements to identify all of the possible things our system could do to help the user.
Analyze Requirements: Based on the Requirements we have elicited, we must clarify, refine, and resolve conflicts in these requirements. Often we will have far too many "requirements" and we must identify which of those are core requirements, versus just nice-to-have feature enhancements.
Document Requirements: When we have identified core requirements, we must record them in a clear, unambiguous form.
Validate Requirements: We must finally validate that these requirements accurately reflect the needs of the users, and ensure we haven't missed anything.
Use Cases
One tool we use in requirements gathering is the use case. A use case describes a specific interaction between a user (often called an "actor") and the system. The goal is to capture what the user wants to accomplish, rather than how the system will do it. As an example, we might make the following Use Case for a LMS
Actor: Student
Goal: View Grades
User Flow:
- Student logs into the system.
- Student searches for the desired course.
- Student accesses course.
- Student accesses Gradebook.
- System displays grades to student.
Use cases are powerful because they:
- Keep requirements grounded in user needs.
- Help identify edge cases and exceptions.
- Provide a bridge between business goals and system design.
Actor
The Actor represents the entity accomplishing something due to our system. Usually this is a person (such as a student, or faculty member), but can also be another system or service for example in the context of an API.
Goal
The Goal represents the core objective the Actor wishes to accomplish. This defines the essential outcome of the requirement. If that outcome isn't met, the requirement hasn't been achieved.
For example, in our grades example above the goal is to view grades.
- If the system displays the grades as a percentage, this goal has been achieved.
- If we also add an exclamation point indicator when a grade is considered a failing grade, this is additional feature enhancement that goes beyond the core requirement.
Preconditions
Preconditions are the conditions that must already be true before the use case can begin. They represent assumptions that the system and stakeholders are relying on when defining requirements. For example, in the Use Case we defined above, there's a hidden precondition we glossed over which is that the student must be enrolled in some course, and they must have done something to see a grade.
Actor: Student
Goal: View Grades
Precondition: Student is enrolled in a course, has completed some graded work.
User Flow:
- Student logs into the system.
- Student searches for the desired course.
- Student accesses course.
- Student accesses Gradebook.
- System displays grades to student.
As you can see, it's very easy to gloss over certain preconditions as they're assumed to be obvious or implied, but part of successful requirements gathering is not making assumptions without validating they're safe to make. However if the student isn't enrolled, the use case fails before it even starts! This is why accurately understanding your preconditions matters.
Main Flow / User Flow
The Main Flow (sometimes called the User Flow) defines the sequence of steps the user takes along the expected path to completion. This is often referred to as the "Happy Path": if everything goes as intended, the Actor will follow these steps and at the end will have achieved their goal.
Postconditions
The Postconditions are the outcome of a single use case. They define what the state of the system is after the use case completes successfully. We could further expand our example above:
Actor: Student
Goal: View Grades
Precondition: Student is enrolled in a course, has completed some graded work.
User Flow:
- Student logs into the system.
- Student searches for the desired course.
- Student accesses course.
- Student accesses Gradebook.
- System displays grades to a student.
Postcondition:
- User understands and is aware of their grade.
- Why do we use the term Actor instead of simply saying User?
- Think of a program you use regularly. What would you consider its Happy Path?
- Can you recall a time when you went off the Happy Path in a program? How did the program handle it?
- In the example above of a postcondition, how could the Main Flow ever be completed without the postcondition being true?
Functional vs. Non-Functional Requirements
In Requirements Gathering and Analysis we have to differentiate between Functional Requirements, and Non-Functional Requirements.
Functional Requirements describe what the system should do.
Non-Functional Requirements describe how well the system provides those Functional Requirements.
The Functional Requirements are the core objective that your Software must achieve. They're the "must-haves". Non-Functional Requirements on the other hand are everything else that goes into your software, including security, usability, accessibility, etc.
Systems Design
Systems Design is the phase where requirements are translated into technical solutions. It's the blueprint for development. This phase sets the foundation for how the system will function, scale, and evolve throughout its lifecycle.
When in the Systems Design phase, you must ensure the system is maintainable, able to scale to the number of users you anticipate, and meets the functional and non-functional requirements you have identified.
Principles and Trade-Offs
In Systems Design, you are constantly making trade-offs. There are multiple, often competing priorities that must be balanced in order to create a system that meets its requirements, while managing complexity and resource constraints.
Certain non-functional requirements related to System Quality are often called the "-ilities" and refer to how capable a system is in various ways. For example, "accessibility" refers to how accessible a site is. If you're interested, Wikipedia has a fairly extensive List of System Quality Attributes and they're worth a read!
Trade-Off Example
Imagine you are building Point-of-Sale (POS) system for a retail store. The core functionality is to handle in-person transactions. To accomplish this, you'll need to ensure your software can integrate with your debit/credit machine, open the cash register, and managing inventory. Since this is critical, it's the first thing you'll focus on in the design phase.
Maybe those non-negotiables will take you around 40h of work to accomplish. You could theoretically start using this POS Software after a week! You could launch this, and the retail store will be able to begin processing transactions.
However: your software crashes after every transaction, and the system takes about two minutes to restart each time. While the POS Software is technically working, this performance issue might be a dealbreaker in the real world. Let's suppose solving this problem would take 10h to resolve.
Also while doing initial testing, you realize having to manually update prices via the POS directly is frustrating and takes the machine out of commission for the time it takes to update. So based on user feedback, you decide adding the ability to do remote price updates would be a good feature. Let's suppose that adding this feature would be another 10h of work.
This is a fairly simplified example of the trade-offs that are always present in systems design.
- Do you launch with the basic functionality and opt to fix issues as they arise? (Risking user frustration)
- Do you invest more time up front to make the system more reliable and easier to maintain, but at the expense of a delay in shipping "out the door"?
- Do you prioritize new features like remote price updates, knowing they'll improve the software in the future?
These dilemmas are constantly present in systems design. You'll always need to make decisions and trade-offs and decide what's important to your software, and what can be deferred. As a Systesm Designer, you need to evaluate these requirements and decide where you can compromise, and where you must invest.
Role Highlight: Product Manager
The role of a Product Manager is very common in the software industry, and their core responsibility is usually to align and balance customer needs with business goals. This can mean defining priorities, or just ensuring the team is focused on what matters most to the users and the business. For companies which have more than a handful of developers, this coordination becomes a full-time job!
Types of Design
In the Systems Design phase, we typically break down design work into different levels depending on how detailed the work is, and which part of the system we're focusing on. Think of this as the Big Picture vs. a Deep Dive on individual details.
For this course, we'll focus on two levels of design: High-Level Design, and Low-Level Design. This is not a complete picture: the Systems Design field can be expanded on in many different directions and well beyond two levels, but it gives a good foundation for understanding.
High-Level Design
High-Level Design focuses on the overall architecture of the system as a whole. This is where you figure out the major components and how they'll interact with each other. At this level, you should be answering questions like:
- What are the main services or modules?
- How will data flow through the system?
- Is this a web app? A mobile app? A desktop app?
- What type of database will we use?
- Do we need any external data storage, or other APIs or integrations?
This level of design sets the foundation for the system. A good High-Level Design creates boundaries which make it easier to divide work across a team, and ensure all the pieces will eventually fit together.
Low-Level Design
Low-Level Design zooms in on individual components or modules. This is where you figure out the internal logic and structure of each part of the system.
At this level, you should be answering questions like:
- What classes or functions do I need?
- What edge cases exist, and how should I handle them?
- What kind of data structures should be used?
- How should we handle errors, and logging?
This Low-Level Design is helpful for developers who are about to start coding. It removes ambiguity and helps avoid technical debt down the road by clarifying the goals of how things should work before anyone touches their keyboard.
Development, Validation, Testing
The Development, Validation, and Testing phase is where the actual software comes to life.
After the requirements have been gathered and the system has been designed, developers begin writing code and implementing features. This phase is often the most cyclical of the entire Software Development Lifecycle (SDLC), with developers and testers working in close feedback loops.
Unlike earlier phases, which are more about planning and design, this stage is about building and proving.
Development: Programmers translate the system design into working code. This may involve writing new modules, integrating libraries, or extending existing systems. Development can happen in short iterations (Agile) or in longer, predefined cycles (Plan-driven).
Validation: As the code is written, developers and testers confirm that the work meets both the system design and the original requirements. Validation answers the question: "Are we building the right system?"
Testing: Testing ensures the software works correctly and reliably. There are many different types of testing, some of which include:
- Unit testing (testing small pieces of code in isolation).
- Integration testing (checking how components interact).
- End-to-end Testing (verifying the whole system against requirements).
Because development will introduce bugs and unexpected behaviour, development and testing repeats in cycles until the system that is being worked on is stable and meets its goals.
- Why is this phase considered the most cyclical phase of the SDLC?
- What is the difference between Validation and Testing?
- How might development look different in a Plan-Driven methodology vs. an Agile one?
Plan-Driven Methodologies
Plan-Driven methodologies approach software development with a structured, sequential process, in which each phase of the SDLC is carefully planned and completed in order.
This methodology is sometimes called the "waterfall" approach, since work flows downward through distinct stages (Requirements, Design, Development, Testing, Deployment, and Maintenance).
Characteristics of Plan-Driven Approaches
While the Waterfall model is one example of Plan-Driven Methodologies, the core characteristics shared by Waterfall and others include:
Up-front Planning: where Requirements and designs are expected to be complete and stable before development begins.
Sequential: Each phase is completed before the next starts, with limited overlap.
Documentation-heavy: Progress and handoffs between phases are primarily handled through documentation, with documentation being considered the source of truth.
Predictable: Plan-Driven methodologies work best when requirements are well-understood and unlikely to change.
Advantages
Plan-Driven methodologies are less common today, but do have some distinct advantages. The clear structure and milestones makes it easier to identify when something is "Done". This means progress is easier to measure, and so it's usually much easier to identify how a project is going and if it's on or off-track.
Additionally, for projects with tightly regulated requirements, such as medical devices, aerospace devices, or government contracts, the Plan-Driven approach can ensure nothing gets missed, which can be life-or-death in some cases!
Disadvantages
The biggest disadvantage is a lack of flexibility. If requirements change during development, this can be very hard to reconcile. Additionally in many methodologies, testing often happens late which makes errors costly to fix, if they can be fixed at all.
This can result in software that might meet the original plan, but doesn't fit the actual user needs as they evolve.
Common Plan-Driven Methodology Models
The Waterfall Model is the classic example of a Plan-Driven approach. This model divides the process into a series of linear, sequential stages:
- Requirements - All functional and non-functional requirements are gathered and documented in detail.
- Design - System architecture, database structures, and technical specifications are created.
- Development - Programmers translate the design into actual code.
- Testing - Completed software is tested against requirements.
- Deployment - The tested system is released to users.
- Maintenance - The system is updated and supported over time.
Each stage must be completed and documented before moving onto the next. This structure makes the model predictable and easy to manage, but also inflexible. Changes discovered late in the process can be expensive to accommodate.
The V-Model is an extension of the Waterfall approach. It emphasizes that each development stage has a directly corresponding testing stage, arranged visually in the shape of a "V":
On the left side of the V are the planning and design stages (requirements, system design, detailed design).
At the bottom of the V is the Implementation (coding) phase, the "primary phase" where the system is actually built.
On the right side of the V are the testing stages, which mirror the earlier planning:
- Unit testing → validates detailed design.
- Integration testing → validates system design.
- System testing → validates requirements.
- User acceptance testing → validates business needs.
This structure ensures that testing is planned alongside each design stage, rather than being left entirely to the end. Although the execution of tests still occurs after implementation, the test cases are designed much earlier, reducing the risk of missed requirements.
- What are the main characteristics of plan-driven methodologies?
- In what situations might a plan-driven methodology be the best choice?
- How does the V-Model improve on the Waterfall model in terms of testing?
- Why might plan-driven methodologies struggle in projects with rapidly changing requirements?
Agile Methodologies
Agile is a philosophy built around flexibility, as change is assumed to be inevitable.
Agile emerged as a reaction to the rigidity of Plan-Driven methods like Waterfall. Where Plan-Driven assumes requirements can be fully understood up front, Agile assumes that change is inevitable and that software should evolve alongside user needs.
Characteristics of Agile Approaches
Agile has many specific frameworks (Scrum, Kanban, XP, etc.), which all share a common set of characteristics:
Iterative and Incremental: Work is divided into small cycles (often called iterations or sprints), with each cycle producing working software.
Customer Collaboration: Stakeholders and end-users are involved throughout the process to ensure the product meets evolving needs.
Working Software over Documentation: While documentation isn't ignored, Agile prioritizes delivering usable software as the main measure of progress.
Flexibility and Adaptability: Teams are expected to respond quickly to new information, changing requirements, or shifting priorities.
Cross-functional Teams: Agile teams typically include developers, testers, and sometimes even stakeholders, all working closely together. An Agile team should have all of the skillsets required to achieve its goal.
Advantages
Agile's biggest strength is adaptability. If requirements change, teams can adjust direction quickly without waiting for an entire development cycle to complete. Because Agile delivers working software incrementally, users and stakeholders can see progress early and often, and provide feedback. This leads to products that more closely fit actual user needs. Often, Agile will reduce risk since problems are discovered earlier, and course corrections are built into the process.
Disadvantages
Agile can also introduce challenges. There is less predictability, as without a fixed plan, it can be harder to forecast exact timelines or costs. It also introduces a dependence on communication. Since A requires constant collaboration, weak communication or disengaged stakeholders can cause failure. Additionally, since Agile welcomes change, projects can drift without strong prioritization and discipline which can cause Scope Creep.
Common Agile Methodology Models
Scrum
Scrum is one of the most widely used Agile frameworks. Work is divided into fixed-length sprints (usually 2–4 weeks). At the start of each sprint, the team commits to a set of work items from a prioritized product backlog. At the end of the sprint, the team delivers potentially shippable software and reflects on how to improve in the next cycle.
Scrum is an opinionated framework, and the Scrum Guide outlines this by saying:
The Scrum framework, as outlined herein, is immutable. While implementing only parts of Scrum is possible, the result is not Scrum. Scrum exists only in its entirety and functions well as a container for other techniques, methodologies, and practices.
Kanban
Kanban focuses on visualizing work in progress, often using a board divided into columns (e.g., "To Do," "In Progress," "Done"). Work items move across the board, which helps teams limit how much is in progress at once and identify bottlenecks.
Other Approaches
Other approaches like Extreme Programming (XP) and Lean Software Development emphasize engineering practices (e.g., test-driven development, pair programming) or process efficiency.
What all of these share is the Agile philosophy: adapt, collaborate, and deliver value continuously.
- What assumptions about requirements make Agile different from plan-driven methodologies?
- In what kinds of projects might Agile approaches be the most effective?
- What are potential risks of using Agile if stakeholders are not engaged or available?
Deployment
Deployment is the process of delivering the finished software to its intended users.
After the system has been developed and tested, it must be made available in a real-world environment. Deployment is often the point where careful preparation pays off, since mistakes here directly impact users. Unlike the prior phases which take place in controlled environments, deployment involves moving the software into production where reliability, performance, and user experience are critical.
Want an example of a live deployment pipeline? Why not take a look under the hood at the COMP340 Playbook? In the GitHub repository you can see:
- The use of mdbook, a tool written by others that I used to create this book.
- CICD Pipeline using GitHub Workflows in ./github/workflows
- Handling of third-party dependencies: I wanted colourful "callouts" like you're reading now and the mdbook defaults weren't very good, so I installed mdbook-admonish.
Deployment Planning: Before release, teams prepare for how and when the system will go live. This may involve setting timelines, coordinating with stakeholders, and planning communication to users.
Deployment Environments: Software typically moves through several environments such as development, testing, staging, and production - before reaching end users. These stages help ensure issues are caught early and not released into production.
Deployment Models: Different approaches exist for putting software into production. For example, a system might be rolled out gradually to a subset of users (canary release), or updated all at once across the entire user base. Cloud-based systems also allow for flexible and scalable deployment strategies compared to traditional on-premises releases.
Post-Deployment Verification: Once software is live, teams must confirm it is working as intended. This may include smoke testing (quick checks of critical functionality) and monitoring system health through metrics and logs.
Generally speaking, deployment is not a single event but part of an ongoing cycle, especially in modern development practices where frequent, small releases are common.
Deployment vs Release
When we refer to Deploying and Releasing something, often the terms are used interchangeably. However, they describe two slightly different parts of how software reaches our users.
Deployment refers to putting software into a production environment where it could be used. This doesn't necessarily mean users see it right away. For example, we might deploy a new feature to a production server but hide it behind a feature flag, keeping it invisible to most users until it's ready.
Release is the moment that deployed software is made available to end users. Releasing is about visibility and access. Releases can be broad, where everyone gets it at once, or controlled where only certain users or regions see it at first.
- Why is deployment considered one of the riskiest phases of the SDLC?
- What is the purpose of having multiple deployment environments (development, staging, production)?
- How does a canary release differ from deploying to all users at once?
Maintenance
Maintenance is the process of keeping software reliable, secure, and useful after it has been deployed.
Once software goes live, the work isn't done. Real users will uncover issues that weren't found during testing, technology will change, and requirements will evolve. Maintenance ensures the system continues to meet its goals long after the initial release. Maintenance consumes the majority of a software system's total cost over its lifetime. The effort to sustain software usually outweighs the effort to build it.
Value and Negative Value
In the context of Systems Design and Analysis, you can think of the contribution of each feature towards your core objective as value-adds. Picture designing a vacation rental booking service: the ability to book a rental at all is a core requirement and is essential, but the ability to filter based on price is a value-add, it's non-essential but desirable. If you can only spend $100/night, there's no point wading through dozens of listings of $350/night! From a business perspective, giving users this extra feature will likely increase the usability of your site, and make it more likely they spend money.
On the opposite side of the coin is the idea of something with negative value. One type of negative value can be a poorly conceived feature. A "location randomizer" mode in which you place a booking and get booked for a random place might sound great to a very select group of adventurous types, but most people would find that feature irritating at best and dishonest at worst.
More common examples of negative value will be bugs, complexity, confusion, or just general poor usability. Imagine if you had to take a photo and submit your ID for every booking because our application chooses not to handle personally identifiable information (PII). That decision to reduce risk by not handling PII has a distinct benefit to the business, reduced cost. However this comes at the expense of a poor user experience.
Maintenance Types
Corrective Maintenance: Fixing bugs, errors, or vulnerabilities that show up in production. Even well-tested systems eventually encounter unexpected problems.
Adaptive Maintenance: Updating the system to work in new environments, for example, upgrading to new operating systems, browsers, or hardware platforms.
Perfective Maintenance: Improving or extending the system based on user feedback, such as adding new features or streamlining workflows.
Preventive Maintenance: Making proactive changes to reduce the chance of future problems, like refactoring code, improving documentation, or addressing technical debt.
Challenges
Over time, systems accumulate complexity. Legacy code, shifting requirements, and limited resources can all make maintenance difficult. In the worst case, Balancing urgent fixes against long-term improvements is part of the discipline. Maintenance is not just about "fixing what's broken," but about sustaining and evolving software so it remains valuable for years to come.
- Why does maintenance often account for the majority of software costs over time?
- What is the difference between corrective and preventive maintenance?
- How does technical debt affect the long-term maintenance of a system?
AI In the Software Development Lifecycle
AI Software Development Tools are another advancement in the developer's toolkit, but they are not a replacement for developers.
Artificial Intelligence (AI) has become part of modern software development. Tools that can generate code, assist with testing, or analyze project artifacts are already in widespread use. While these tools can be valuable, they also bring new risks and challenges. AI is here to stay in the software development world. Even if progress on these tools stopped today, the current generation of tools already allows developers to accomplish more work in less time than before. Despite this, developers still need to apply critical thinking, technical expertise, and domain knowledge to avoid making design or implementation decisions they later regret.
How AI Fits Into the SDLC
Depending on the organization and the development process, AI can appear at many points in the Software Development Lifecycle:
-
Requirements Gathering & Analysis - summarizing documents, highlighting ambiguities, or suggesting missing requirements.
-
Systems Design - generating draft diagrams or code skeletons, though designs must still be validated by developers.
-
Development, Validation, and Testing - providing code completions, examples, or boilerplate. Generating test cases, identifying possible vulnerabilities, or flagging edge cases.
-
Deployment - monitoring logs or detecting anomalies in system behaviour.
-
Maintenance - suggesting bug fixes, dependency updates, or performance optimizations.
Benefits
The biggest advantage to using AI in development is productivity. When used carefully, it can help reduce time spent on repetitive or boilerplate tasks. AI suggestions can help break analysis paralysis, but the proposed path may not always be optimal.
AI also provides a form of support. While reading the documentation is still a very important skill that should not be replaced with AI, it can help with understanding high-level concepts or reframing things to make it easier to understand.
Risks
On the converse side, AI has some significant risks. These risks can be mitigated, but if you aren't active in managing them they will negatively impact your development. For example, the accuracy of AI-generated code is often poor. This is typically due to a lack of context, but also can be caused from the tool making incorrect assumptions such as whether a given method exists or not. AI outputs may also introduce security vulnerabilities or licensing issues if not carefully reviewed.
One of the biggest issues with over-using AI tools is an Overreliance on the tool which can negatively impact your way of thinking. Developers need to stay engaged in problem-solving themselves, and actively thinking and trying to understand things for yourself without an AI summary at the end is a very important skill!
In Summary
AI is a tool, not a replacement for developers. It can boost productivity, support learning, and help uncover issues, but it comes with real risks like inaccuracy, overreliance, and potential security or licensing problems.
Is there anything else I should add to this document to ensure students fully understand why not to rely on AI blindly?
Just kidding. The most important thing while using these tools is to apply your own judgement, be transparent with where and how you use the tools, and to stay actively engaged in problem-solving.
- What risks could arise if developers rely too heavily on AI-generated outputs?
- How do AI tools differ from traditional automation tools in development?
Static Design
Static Design is about organizing the solution before we bring it to life.
In Systems Analysis, we focused on understanding the problem, and figuring out what we need to build and why. Now, in Static Design, we shift gears and start thinking about how the solution will be structured.
If Systems Analysis is like designing the floor plan for a house, Static Design is when we decide how the walls fit together, where the electrical outlets go, and how the plumbing connects. It's not the final build yet, but it's when the abstract ideas start turning into a tangible structure.
We call it static because it deals with the parts of the system that don't move. These are the nouns of our software:
- Classes
- Objects
- Entities
- Relationships
- Attributes
These are the components that define your program, rather than define how your program behaves.
When You Skip Static Design
When developers skip Static Design, you can usually tell. The biggest risk in avoiding Static Design is a lack of coherency, which can make it challenging to develop without painting yourself in a corner.
You may see:
- Classes named after UI buttons instead of concepts
- Databases full of vague tables like data or info
- Multiple parts of the system describing the same thing slightly differently (user, person, customer, account_holder)
It's not that these programs can't work, but they become hard to maintain because no one's quite sure where the "truth" lives.
What We'll Learn
In this module, we'll explore how to build a clear, consistent model of our software's structure.
That includes:
- Domain Modeling – describing the real-world concepts the software represents.
- Database Design – turning those concepts into tables, fields, and relationships.
- Object-Oriented Design Patterns – reusable approaches for structuring and connecting parts of a system.
- Class Diagrams – showing how entities or classes are related in UML.
- Sequence Diagrams – illustrating how those parts might eventually interact.
By the end of this module, you'll be able to look at a problem and sketch out a solid foundation for any application. This foundation should be something that's technically sound and easy to reason about.
Domain Modeling
Domain Modeling is about capturing the real-world concepts your system needs to understand.
Before we can build a database or write classes, we need to make sure we actually understand the world our software represents. Beyond just the classes, or database tables, or functions - what are the real concepts we're trying to model?
The "Domain"
A domain is just the problem space you're working in. This is what your software needs to understand.
For a few examples:
- A banking system deals with accounts, customers, and transactions.
- Blackboard's model deals with students, courses, and grades.
- A video game might deal with characters, items, and levels.
Each of these has its own rules, relationships, and concepts. It's these ideas which we want to capture in our domain model.
When developers misunderstand the domain, we end up building the wrong abstractions. For example, imagine building a "Course Registration System" where a Student can only have one Course at a time. That might technically work, but it fails to reflect reality which is that students take many courses.
A domain model helps us avoid those mistakes by making the system's assumptions visible and reviewable.
How to Build a Domain Model
Building a domain model is about discovery and refinement. We need to:
- Identify the main concepts (entities)
- Skim through requirements, user stories, or even conversations.
- Look for the nouns. Student, Course, Instructor, Invoice, etc.
- List the details (attributes)
- What information do we need to track for each one?
- e.g. Student → name, email, student_id
- Map the relationships
- How do these entities connect?
During this whole process, you should ensure to validate this model with real people. Your stakeholders should agree this is how the entities are modeled, so that you can be confident in the implementation you're moving forward with.
| Entity | Attributes | Relationships |
|---|---|---|
| Student | Name, Email | Enrols in Courses |
| Course | Title, Credits | Taught by one instructor; Has students |
| Instructor | Name, Department | Teaches courses |
A simple visual model might look like:
[Student] *-- enrolls in --* [Course] *-- taught by --1 [Instructor]
At this stage, this isn't code or a database schema yet. It's just a map of how the system thinks about the world.
In class, we will model a Library's inventory system using Mermaid.js.
Database Design
Database Design is about modeling how the actual data required by your system will be stored and retrieved.
Previously, with domain modeling, we were thinking about the real-world concepts that our system needed to understand to be implemented. With the database model, we need to start thinking about how these concepts get mapped and stored, and how they relate to each other.
We usually design databases before we start coding specifically so that we can challenge our assumptions about how the pieces will fit together. When thinking at the domain modeling level, it's easy to hand-wave away problems by saying that you'll get to that later. When designing a database, it's much more concrete as you have to connect the various tables together in a way that makes sense, but it can still be changed very easily before anything is concretely implemented.
Domain Model -> Database Model
When transitioning from a Domain Model to a Database Model, we will:
- Map Entities to Tables
- Map Attributes to Columns
- Represent Relationships
Representing Relationships is where the main differences between Domain Modeling and Database Modeling show up. In Domain Modeling, we're less concerned with how the relationships themselves work, whereas in Database Modeling, we need to represent those relationships via columns or tables as appropriate. A one-to-many relationship might be defined by a foreign key column, while a many-to-many relationship would involve a linking table to connect both sides.
The Stages of Database Design
When designing a database, it helps to think in layers. We start broad, defining what data needs to exist, and then gradually move toward how it will be implemented. We will describe database design in three stages:
-
Conceptual Design: focuses on identifying entities and how they relate to one another. This stage answers, "What information do we need to store?"
-
Logical Design: focuses on organizing that information into a structured form using tables, attributes, and relationships. Here we define things like primary and foreign keys, data types, and constraints.
-
Physical Design: focuses on performance and implementation details. This is where indexing, storage strategies, and optimization for a specific database engine come in.
By working through these stages in order, we can ensure that our database reflects both the needs of the system and the realities of how it will run in practice. Each stage helps validate the previous one before we commit to code or production.
Entities, Attributes, and Relationships
A database is made up of three key building blocks: entities, attributes, and relationships.
Entities represent the things we need to keep track of - like Students, Courses, or Instructors. In a relational database, each entity typically becomes a table.
Attributes are the details we want to record about each entity - like a student's name or a course's title. These become columns within the table.
Relationships describe how entities are connected - for example, a Student enrolls in a Course.
Relationships give structure and meaning to the data. Without them, our database would just be a collection of unrelated lists. Representing these connections accurately is one of the most important parts of the design process.
When modeling entities and relationships, think carefully about cardinality (one-to-one, one-to-many, many-to-many) and optionality (whether a relationship must exist). These details determine how your tables and constraints will be defined later.
Designing with Relationships in Mind
Relationships are what make a database relational. Understanding how to represent them correctly is key to building systems that reflect the real world accurately.
In a one-to-one relationship, each record in one table corresponds to exactly one record in another. For example, a User might have exactly one Profile. In a one-to-many relationship, one record relates to several others - a Course can have many Enrollments. In a many-to-many relationship, records on both sides can be connected to many others - a Student can enroll in many Courses, and each Course can have many Students. These relationships require a linking (junction) table to connect them.
To maintain consistency, we use foreign keys - columns that reference primary keys in other tables. Foreign keys enforce referential integrity, ensuring that relationships remain valid even as data changes. Many database systems also let you specify what should happen when a related record is deleted or updated (for example, cascading the change or preventing it).
Practical Schema Design Considerations
Once you've mapped out your tables and relationships, it's time to make design decisions that affect usability, performance, and maintainability. Some best practices include:
-
Naming conventions: Use consistent, clear names (e.g., student_id instead of just id when it's a foreign key).
-
Choosing data types: Pick data types that match how the data will be used - use integers for IDs, dates for timestamps, and strings of reasonable length for text fields.
-
Using constraints wisely: Apply NOT NULL, UNIQUE, and CHECK constraints to enforce data rules directly in the database.
-
Indexing: Add indexes to columns that are frequently searched or joined on, but avoid adding too many as they can slow down inserts and updates.
-
Audit fields: Include created_at, updated_at, and sometimes created_by or updated_by columns for traceability.
-
Avoiding common pitfalls: Be cautious with NULL values, overly generic column names, or fields that combine multiple pieces of information.
Good schema design is about finding balance - between strictness and flexibility, normalization and performance, simplicity and completeness. A well-designed schema will make your system easier to work with and evolve over time.
In class, we will expand on our model of a Library's inventory system using dbdiagram.io.
Object-Oriented Design
Object-Oriented Design is about modeling how our system's components will behave and interact in code.
So far, we have:
- Discussed the domain and thinking about how the entities themselves are defined.
- Discussed the database and how the entities relate to one another.
Now, with Object-Oriented Design (OOD) we are looking to wrap the entities, their relationships, and the behaviours of those entities together.
Mapping Entities to Objects
In Object-Oriented-Design is where we take the real-world ideas from our domain model and start shaping them into classes, objects, and methods that can actually run in a program. The goal is to design software that's organized, reusable, and easy to maintain.
When transitioning from a Domain Model to an Object Model, we move from describing "what exists" to describing "how it works."
In this stage, we:
- Map Entities to Classes
- Map Attributes to Properties or Fields
- Map Relationships to References or Collections
- Add Behaviours that define what objects can do
For example, if our domain model has a Book entity with attributes like title and author, and a Library entity that contains many books, our object model might define a Library class that includes a books list and a checkOut() method.
This step helps us visualize how data and logic come together, and how our system behaves.
The Core Concepts of Object Orientation
Object-Oriented Design is built around a few foundational ideas:
- Encapsulation: Keeping data and behaviour that belong together inside the same class.
- Abstraction: Hiding unnecessary details so we can focus on what an object does, rather than how it's done.
- Inheritance: Allowing one class to share behaviour and structure with another.
- Polymorphism: Letting different classes respond to the same action in their own way.
These ideas help make systems easier to extend, modify, and understand as they grow in size and complexity.
Common Design Patterns and Principles
As you progress through the industry, you'll start to recognize common problems that appear across many projects. Design Patterns are reusable solutions to these recurring challenges.
For example:
- A Singleton ensures only one instance of a class exists.
- A Factory helps create objects without specifying their exact types.
- An Observer lets multiple objects stay updated when something changes.
These patterns are built on the same OOD principles of abstraction, encapsulation, and clear responsibility. They provide a shared vocabulary for discussing design decisions.
- When designing your classes and interactions, start from the domain. Design classes that directly reflect the key concepts in your system.
- Keep responsibilities small and clear: Each class should have one clear purpose. Combine small, focused classes instead of building deep hierarchies.
- Think about how objects collaborate: Use method calls and relationships to model real-world interactions.
Good Object-Oriented Design makes software more flexible and intuitive to work with. The more carefully you plan these relationships, the easier development and maintenance become down the line.
Sequence Diagrams
Sequence Diagrams are about modeling how different parts of a system interact over time to accomplish a specific task.
Where Class Diagrams are showing the structure of a system, sequence diagrams are how we begin showing dynamic behaviour, how objects communicate to make something happen. They help us visualize the flow of messages between objects, methods, or systems in a particular use case.
A Sequence Diagram represents a scenario as a series of interactions arranged vertically over time. Each object or component involved is shown as a lifeline, and the messages they send to each other are shown as horizontal arrows.
Think of it as a timeline that shows who talks to whom, in what order, and what the result is.
Elements
A Sequence Diagram is made up of a few elements:
Actors: Represent external users or systems that start the interaction (e.g., a User).
Objects (Lifelines): Represent parts of the system that participate in the interaction (e.g., LibrarySystem, Book, Database).
Messages: Arrows showing communication between lifelines. These can represent method calls, signals, or data requests.
Return Messages: Dotted arrows showing responses or results returned from an operation.
Usage
Sequence Diagrams are most useful when you need to:
- Clarify the flow of a specific use case
- Identify which classes or components handle which responsibilities
- Communicate how data and control move through the system
- Validate that your design matches real-world behaviour.
Like most of the diagrams we've spoken about so far, they are very useful in early design discussions, where having a simple visual aid can be helpful in revealing overly complicated interactions before you've committed to building them.
To create a Sequence Diagram, you should start with a clear scenario. Identify the main actors and components, and then map out the messages that are required to occur, and the order in which they occur.
- Identify participants: who's involved in the interaction?
- List the key steps: what happens from start to finish?
- Draw lifelines for each participant.
- Add messages between lifelines to show communication flow.
- Include activations and returns to show when and how each participant responds.
Dynamic Design
Dynamic Design focuses on how the system we are developing behaves over time. In contrast to static design which is describing the system and its relationships, dynamic design is describing the behaviour. This includes interactions, workflows, and runtime processes.
Sequence Diagrams from Module 2 are a good introductory concept to Dynamic Design. Over the course of this Module, we will also cover Workflow Modeling, State Modeling as two methods of communicating ideas specific to dynamic design. Further, we will dig into System Integration and Performance and Scalability.
By the end of this module, you should be able to make solid design decisions regarding the performance of the code you're writing. You should also be able to effectively communicate the basic concepts of interactivity in your programs.
Workflow Modeling
In Workflow Modeling, what we are working to describe is:
- What Happens?
- In What Order?
- Under What Conditions?
- By which actors?
This can be done using basic flowcharts, swimlane flowcharts, process maps, and more UML-specific tools such as Activity Diagrams.
General-Purpose Workflow Flowcharts
Flowcharts are the simplest and most flexible way to model a process. They use basic shapes to model actions, decisions, and when the process starts/ends. Flowcharts are excellent for:
- Describing straightforward, step-by-step processes.
- Modeling deicison points. (If valid -> do X, else do Y.)
- Illustrating loops or repeated actions.
- Communicating logic to beginners or non-technical audiences.
Basic Flowcharts have some key elements:
- Start/End Nodes - Which indicate where this specific process begins and concludes.
- Action Steps - Representing tasks, operations, or activities.
- Decision Points - Indicating a branch in the workflow based on True/False outcomes.
- Flow Lines - Showing the direction of the process from each step to the next.
Copy-paste the following flowchart code into a mermaid.js-compatible editor. Modify the flowchart to prompt users to re-submit rather than reject their request entirely.
flowchart TD
A([Start]) --> B[Receive request]
B --> C{Is the request valid?}
C -->|Yes| D[Process request]
C -->|No| E[Reject request]
D --> F([End])
E --> F([End])
Adding Actors
In the basic example above as part of our in-class activity, the flowchart describes a process without any real mention of actors. The user is implied by the system prompting for re-submission, but is never explicitly drawn out.
This can impact the readability of a diagram and even the design of a program because the concept of responsibility isn't clear. When we prompt the user for re-submission, what happens when they just don't respond?
When diagramming, this is solved by defining who the actors are for a given action. Rather than just "Submitting Request" -> "Is Request Valid?", the user actor will submit the request, the system actor will receive the request, and then the request submission's validity will be determined.
These concepts of Actors can be modeled in Mermaid's flowcharts using subgraphs.
The following example shows actor 'lanes' modeled using subgraphs in Mermaid.
flowchart LR
%% User lane
subgraph User["User"]
U1([Start])
U2[Submit Request]
U3[Re-submit Request]
end
%% System lane
subgraph System["System"]
S1[Receive Request]
S2{Is Request Valid?}
S3[Process Request]
S4[Prompt User to Re-submit]
S5([End])
end
%% Flow between lanes
U1 --> U2 --> S1 --> S2
S2 -->|Yes| S3 --> S5
S2 -->|No| S4 --> U3 --> S1
Activity Diagrams and Formal Specifications
Different softwares have different ways of modeling these specifications to try and keep the syntax consistent. UML Actiivty Diagrams are one example which are particularly well suited for:
- Showing concurrency or parallel processes.
- Modeling object flow through a system.
- Modeling multiple different actors, where responsibility must be clear.
UML's Activity Diagrams formalize the flowcharts further adding forks and joins to show parallel workflows, swimlanes to model actors or other responsible parties, and object nodes which represent data or artifacts moving throughout the process.
The following documentation outlines how Activity Diagrams are modeled and the UML syntax for them. In particular, the UML concepts of a Fork are very useful for modeling parallel workflows.
State Modeling
State Modeling focuses on how a system changes over time, and the rules that govern those changes.
In State Modeling, our primary focus is on how the system behaves as it transitions from one situation (state) to another. This perspective is especially useful for features or components whose behavior depends on what has happened before.
State models help us answer questions such as:
- What possible states can this system or object be in?
- What events or conditions cause transitions between states?
- What constraints or rules limit how the system may change?
- What behaviour occurs when entering or leaving a state?
State models describe behaviors of systems. In the real world, many systems have complex rules about what can happen when. State modeling attempts to break these rules down and make them explicit.
Diagraming state models can help teams:
- Understand edge cases and error paths
- Identify invalid sequences (e.g., trying to "submit" something that isn't ready to be submitted)
- Simplify complex logic into clearly defined modes
- Communicate how a system should respond to inputs
- Support testing by outlining expected transitions and constraints
In software engineering, State modeling is widely used in UI design, authentication systems, games, embedded systems, and any domain where rules depend on timing or past events.
State Machines and State Diagrams
The most common tool for state modeling is the Finite State Machine (FSM) - a model where a system:
- Has a finite set of states
- Can be in exactly one state at a time
- Responds to events that trigger transitions
A State Diagram visualizes these ideas using:
- States (circles or rounded rectangles)
- Events that trigger a move (arrows)
- Entry / Exit actions (what the system does as it enters or leaves a state)
- Guards or conditions that must be true for a transition to occur
States
A state represents a situation or mode of the system. Conceptually, from your databases class you can think of it sort of like enums. For example:
- A user account may be: Active, Locked, Pending Verification
- A traffic light may be: Green, Yellow, Red
- A document may be: Draft, Submitted, Approved, Archived
Good states describe meaningful differences in behaviour rather than being simply stored values. Using the traffic light example above, the actual colour of a traffic light is not particularly important. What does matter is driver's ability to differentiate between the colours. The information the "red light" is conveying isn't colour, it's the state that traffic coming from a given direction should be in: stopped.
Events
Events trigger attempts to transition between states.
Examples:
- A user clicks “Submit”
- A timer expires
- A car arrives at a red light's sensor.
An event does not always cause a transition, because guards may block it.
Transitions
Transitions define how the system moves from one state to another.
Current State + Event + Guard → New State (with optional actions)
Transitions may also contain:
- Entry actions (run when entering a state)
- Exit actions (run when leaving a state)
- Transition actions (run during the move)
Guards (Conditions)
A transition may only occur if certain conditions are true.
For example:
[Submitted] --(approve)--> [Approved]
[Only if user.role == "Manager"]
Guards prevent invalid sequences and make business rules explicit.
Using State Modeling
State Modeling is most helpful when:
- Behavior depends heavily on history
- There are many edge cases
- Certain actions must be prevented in some states
- The system involves modes (e.g., "editing", "reading", "locked", "error")
- You want to clarify complex logic before implementation
Some examples of state models you may be familiar with include:
- Login/Logout flows (Is the actor logged in or logged out?)
- Multi-step forms (Which step of the form is the actor interacting with?)
- Payment or billing lifecycle
- Game objects (enemy AI difficulty, player status)
- Media players (playing, paused, buffering, stopped)
In-Class Example: Login Flow (Simplified)
The following Mermaid Diagram presents a simple login state model. Based on the model, answer the following:
stateDiagram-v2
[*] --> LoggedOut
LoggedOut --> LoginFlow: log in
state LoginFlow {
[*] --> EnteringCredentials
EnteringCredentials --> Authenticating: submit
Authenticating --> EnteringCredentials: invalid credentials
Authenticating --> EnteringCredentials: network error
Authenticating --> [*]: success
}
LoginFlow --> LoggedIn: success
LoggedIn --> LoggedOut: logout
- How many states exist in the diagram, and what are they?
- What other states might exist?
- How might we use the "Logged In" or "Logged Out" states?
- If an actor is in the "Authenticating State", are they also implicitly in any other states?
Clear State Modeling
Modeling States, like much of the content in this course is about communicating to others. With State diagrams, achieving that involves:
- Keeping states meaningful and distinct
- Avoiding "data-values-as-state" (e.g., "count=5" isn’t a state)
- Avoiding overloading stated. Instead, they should be split them if behaviour differs
- Using guards where applicable to prevent invalid state transitions.
- Including entry/exit actions only when they add clarity
In general, you should always validate your diagram with "What happens if...?" scenarios.
System Integration
System Integration is about how separate components, services, or subsystems work together as one cohesive system.
When building software, few components operate in isolation. A web app interacts with an authentication provider. A service calls a database. A mobile app syncs with an API. Each of these interactions introduces complexity since they add failures, delays, retries, mismatching expectations, and partial results.
Interactions as States
Every interaction between systems can be viewed as a mini state machine:
- A request is initiated
- It becomes pending
- It succeeds, fails, or times out
- The caller responds to the outcome with its own state change
Using state models to represent these integrations can help with communication as it shows what states external systems must reach before an interaction can continue. This is especially important in distributed systems, where timing, latency, and partial failures are common.
Different Systems, Different States
One of the main challenges when integrating systems with each other is that each system is tracking its own states. When these systems interact, the task becomes keeping state transitions consistent with each other and understanding how these states map to each other. When systems interact, they form a combined state model.
For example, consider a simple case:
User places an order → Order Service communicates with Payment Service.
Each subsystem has its own states. The Order Service might track the order through the states of Draft, Submitted, Payment Pending, Completed, Cancelled whereas the Payment Service might track its own states as Not Started, Authorizing, Authorized, Declined, Error.
stateDiagram-v2
[*] --> Draft
Draft --> Submitted: submit order
Submitted --> PaymentPending
state PaymentPending {
[*] --> NotStarted
NotStarted --> Authorizing: begin authorization
Authorizing --> Authorized: success
Authorizing --> Error: service error
Authorizing --> Declined: declined
Declined --> NotStarted: retry
Error --> Declined:
}
PaymentPending --> Completed: payment authorized
PaymentPending --> Cancelled: user cancellation
Completed --> [*]
Cancelled --> [*]
There's a great article on Load Balancing that I quite like that I think both explains an interesting concept in a great way, as well as demonstrates servers in different states in a simplified version of a complicated system.
Performance and Scalability
Performance and Scalability describe how well a system responds under load, and how effectively it can grow to handle increasing demand.
In real software systems, correctness alone is not enough. An application may function perfectly under light use but fail, slow down, or become unstable when many users interact with it simultaneously. Performance and Scalability focus on designing and evaluating systems so they remain responsive, stable, and cost-effective as demand changes.
Performance
Performance relates to the speed and responsiveness of a system. Typical questions include:
- How fast can the system respond to a request?
- Is the system meeting its target response times?
- Does performance degrade as the workload increases?
Common metrics:
- Response Time (how long a request takes)
- Throughput (how many operations per second the system can handle)
- Latency (how long it takes for data to move through the system)
- Resource Utilization (CPU, memory, disk, and network usage)
Good performance ensures that users experience the system as smooth and responsive.
Scalability
Scalability describes how well a system can grow with increased demand. A scalable system can handle more users, more data, or more requests by adding more resources.
The two most common approaches are Vertical Scaling and Horizontal Scaling.
Vertical Scaling: Adding more power to an existing machine (bigger CPU, more RAM).
Horizontal Scaling: Adding more machines or service instances.
Scalability is about maintaining performance while demand increases. While scaling, the system will encounter bottlenecks along the way as a system's speed is dictated by its slowest component. Common bottlenecks are in the database, the network, disk access, and external dependencies such as another API or a payment service.
Identifying bottlenecks is a major part of performance analysis, and so working to keep these bottlenecks as visible as possible is a key part of a developer's job.
Optimization Strategies
Many performance issues can be mitigated through:
- Caching frequently accessed data
- Query Optimization
- Load Balancing
- Asynchronous Processing
- Reducing unnecessary computation
We will expand on many of these concepts in our final presentations.
When optimizing your systems, this should be guided by profiling and measurement rather than guessing.
Load Testing: Simulates real user traffic
Stress Testing: Pushes the system beyond its limits
Monitoring & Observability: Tracking metrics, logs, and traces in production
Resources
Diagramming Tools
In this course, we will primarily use Mermaid but other tools are available as well.
| Tool | Description |
|---|---|
| Mermaid | JavaScript-based diagramming library. |
| PlantUML | UML-based diagramming language. |