Mert Özen Further With Every Line
Software Architecture Introduction to Software Architecture — #02

Quality Attributes: Defining Architecture Characteristics with ISO/IEC 25010:2023

Mert Özen Nov 7, 2026 10 min 5 views
Quality Attributes: Defining Architecture Characteristics with ISO/IEC 25010:2023

Architecture characteristics describe not what a system does but how well it does it, and they only produce decisions once tied to a domain goal and a number. The nine characteristics of ISO/IEC 25010:2023 and what changed since 2011, FoSA's classification, their .NET 10 counterparts and the top three chosen for Bookstore.

Version note: The code in this post was built and run with C# 14 and .NET 10.

Guess First

Last month a bot crawled Bookstore's catalog search all night with hundreds of requests per second and wore out the database. The next morning someone on the team added ASP.NET Core's built-in rate limiter and picked the values "to be safe". Here is a trimmed-down version, run as a console app that sends requests to itself:

using Microsoft.AspNetCore.RateLimiting;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.WebHost.UseUrls("http://127.0.0.1:5199");

builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    // Picked "to be safe" after a bot crawled the catalog
    options.AddFixedWindowLimiter("search", limiter =>
    {
        limiter.PermitLimit = 3;
        limiter.Window = TimeSpan.FromSeconds(10);
        limiter.QueueLimit = 0;
    });
});
builder.Services.AddHealthChecks();

WebApplication app = builder.Build();
app.UseRateLimiter();
app.MapGet("/books/search", (string q) => Results.Ok(new[] { "Dune", "Emma" }))
   .RequireRateLimiting("search");
app.MapHealthChecks("/health");

await app.StartAsync();

using HttpClient http = new() { BaseAddress = new Uri("http://127.0.0.1:5199") };
for (int i = 1; i <= 5; i++)
{
    HttpResponseMessage response = await http.GetAsync("/books/search?q=dune");
    Console.WriteLine($"search #{i}: {(int)response.StatusCode} {response.StatusCode}");
}

HttpResponseMessage health = await http.GetAsync("/health");
Console.WriteLine($"health: {(int)health.StatusCode} {await health.Content.ReadAsStringAsync()}");

await app.StopAsync();

The limiter is not global; it is attached to the endpoint and shares a single fixed window. How many of the five search requests get through? More importantly: during the back-to-school campaign Bookstore's catalog search receives about 200 requests per second. What does this setting do on that day, and where did the number 3 come from?

Where Did the Number Come From?

search #1: 200 OK
search #2: 200 OK
search #3: 200 OK
search #4: 429 TooManyRequests
search #5: 429 TooManyRequests
health: 200 Healthy

From the fourth request on, everyone gets 429. Because the policy uses no partition key, the limit applies to the whole endpoint, not per person: three searches every ten seconds, for all of Bookstore. On campaign day this setting stops the bot, and every customer along with it. The health check, meanwhile, is green; the system is healthy, it just serves nobody.

There is no bug in the code. What is missing is a sentence that should have come before the code: "During campaigns, catalog search must handle 200 legitimate requests per second; a single client exceeding 20 searches per second should be throttled." Without that sentence, PermitLimit is a guess. The sentence describes a quality attribute: not what the system does, but how well and under which conditions it does it. This post builds a shared vocabulary for writing such sentences.

Functional and Non-Functional Requirements

A functional requirement says what the system does: "a customer can search books by title", "stock is reserved when an order is placed". A non-functional requirement states conditions on how that is done: how fast, how long without interruption, resistant to which attacks, how easy to change. Since most of them end in "-ility" in English, they are also called "ilities": availability, scalability, maintainability, testability.

The name "non-functional" is misleading, because these requirements are not without function; if they are not met, the system loses its function. A catalog that lets nobody search on campaign day is useless even if it "can search" functionally. That is why two terms became prominent in the literature. The SEI (Software Engineering Institute) community and the ISO (International Organization for Standardization) standards say quality attribute or quality characteristic. Richards and Ford's Fundamentals of Software Architecture (2nd edition, 2025; FoSA from here on) prefers architecture characteristic and sets three conditions for something to count as one: it specifies a non-domain design consideration (searching belongs to the domain, the search returning in 300 milliseconds does not), it influences some structural aspect of the design (something in the architecture must change to support it), and it is critical or important to the application's success.

The second condition is a practical filter. "The order total must be calculated correctly" is very important, but it doesn't change the architecture; a correctly written method and good tests are enough. "Catalog search must handle 200 requests per second on campaign day", on the other hand, can trigger structural decisions such as a cache, a read replica or a search engine. Architecture conversations are driven by the second kind of requirement.

The Nine Characteristics of ISO/IEC 25010:2023

If every team invents its own names for characteristics, "performance" means latency for one person and CPU (Central Processing Unit) usage for another. That is why ISO/IEC 25010, the joint standard of ISO and IEC (International Electrotechnical Commission), is useful: it provides a shared list of names. The second edition, published in November 2023, defines the product quality model with nine characteristics and their subcharacteristics. The standard is paid; the list below is based on ISO's official preview pages and arc42's summary of the quality model, with names as they appear in the standard.

02-iso-25010-2023-en.png

Functional suitability is the product providing functions that meet the stated and implied needs of its users; giving accurate results (functional correctness) is part of it. Performance efficiency is working within specified time and throughput limits and using resources efficiently; the capacity subcharacteristic covers upper limits such as the number of concurrent users or the size of the database. Compatibility is being able to share an environment with other products and exchange information with them. Interaction capability is the user being able to complete their task through the interface. Reliability is working correctly under specified conditions for a specified period; availability and fault tolerance live here. Security is protecting information from unauthorized access; resistance, added in 2023, describes continuing to operate while under attack. Maintainability is the effectiveness and efficiency of changing the product; modifiability and testability live here. Flexibility is the product adapting to changing requirements, environments and contexts of use; scalability now lives here. Safety is the product not endangering human life, health, property or the environment.

The rate limiter from the opening touches three places in this tree: capacity (handling campaign load), resistance (continuing to operate under a bot attack) and availability (serving legitimate users). On its own the setting killed capacity and won resistance. That is a small example of the main idea of the next sections: characteristics are not independent of each other.

What Changed from 2011 to 2023?

Most tables you will find online still show the 2011 edition. According to the standard's own foreword, the main differences are:

  • Safety was added as a new characteristic, with operational constraint, risk identification, fail safe, hazard warning and safe integration under it.
  • Usability was replaced by interaction capability, and portability by flexibility.
  • Inclusivity and self-descriptiveness were added to interaction capability, resistance to security, and scalability to flexibility.
  • User interface aesthetics was replaced by user engagement, maturity by faultlessness; accessibility was split into inclusivity and user assistance.
  • The quality in use model, previously in the same document, moved to ISO/IEC 25019, and general usage of the models to ISO/IEC 25002.

For an architect, the two most important changes are that scalability is now an official subcharacteristic and that security gained the "keep serving under attack" dimension through resistance. Both bear directly on Bookstore's campaign-day problem. The reason usability became interaction capability is interesting too: the standard now separates a property of the product itself (how suitable the interface is for interaction) from the outcome of use (whether users are actually productive); the latter belongs to the quality in use model.

FoSA's Classification: Operational, Structural, Cloud, Cross-Cutting

The ISO list is comprehensive, but in an architect's day-to-day conversations FoSA's coarser classification is more practical. The book's second edition sorts characteristics into four groups. Operational characteristics concern how the system behaves while running: availability, continuity, performance, recoverability, reliability, robustness, scalability. Structural characteristics concern the structure of the code: configurability, extensibility, installability, reuse, localization, maintainability, portability, upgradeability. Cloud characteristics, a separate group in the second edition: on-demand scalability and elasticity, zone-based availability, region-based privacy and security. Cross-cutting ones touch everything: accessibility, archivability, authentication, authorization, legal, privacy, security, supportability, usability.

An even more important distinction in FoSA is between explicit and implicit characteristics. Explicit ones are written in the requirements document: "200 searches per second during campaigns". Implicit ones are written nowhere but everyone assumes them: the system is secure, data isn't lost, responses come back in reasonable time. Part of an architect's job is making the implicit ones visible. Nobody at Bookstore wrote a requirement saying "payment details must not leak", but an architecture that doesn't ensure it is unacceptable.

How Do Characteristics Show Up in Code?

Characteristics look abstract, but in .NET 10 most of them have a direct counterpart. These counterparts are a good map for seeing where a characteristic turns into an architecture decision:

ISO/IEC 25010:2023.NET 10 / ASP.NET Core counterpartDecision it requires
capacity, resistanceAddRateLimiter, PartitionedRateLimiterFor whom, how many requests per second, what happens over the limit
availabilityAddHealthChecks, MapHealthChecksWhat "healthy" means, which dependency to check
fault toleranceIHttpClientFactory, DelegatingHandler, timeoutsWhat happens when the payment provider doesn't respond
time behaviourHybridCache, OutputCacheHow stale each piece of data may be
analysabilitySystem.Diagnostics.Metrics, ActivitySource, ILoggerWhat we measure and who looks at it
confidentialityASP.NET Core Data Protection, authorization policiesWhich data is visible to whom
resource utilizationNative AOT (Ahead-of-Time compilation), trimmingIs giving up reflection worth the memory and startup time

The last column is the real message. The framework gives you the mechanism, not the number or the policy. AddHealthChecks is one line, but the answer to "are we healthy if the database is slow?" is an availability decision; the health check post in the ASP.NET Core Web API (Application Programming Interface) series answered exactly this question with the liveness and readiness split. The rate limiter from the opening is the same: the mechanism is right, the number is made up.

Which Ones Really Matter for Bookstore?

You have probably heard "the system must be scalable, secure, highly available, maintainable and performant" in a requirements meeting. The sentence says nothing, because characteristics conflict with each other: every security check adds latency, every point of flexibility adds complexity, every availability measure adds cost. Wanting all of them means prioritizing none of them. FoSA's advice is clear: aim not for the best architecture but for the least worst one, and support as few characteristics as possible. The worksheet in the book has only seven rows for characteristics and asks you to mark three of them as the most important; the goal is to force prioritization.

Let's make that choice for Bookstore using the numbers from the first post. Availability: if checkout stops, money is lost, and campaign days bring in a large share of the year's revenue; top three. Performance efficiency, especially time behaviour: if search and checkout slow down, customers abandon their carts; top three. Modifiability: a team of three deploys weekly, and a second country and a mobile app are coming within eighteen months; keeping the pace of change is in the top three.

Implicit but non-negotiable: security (there is payment and personal data; the subject of the OWASP (Open Worldwide Application Security Project) Top 10 series) and functional correctness (money and stock can't be calculated wrong). They are not on the "top three" list because they are not up for debate; every architecture option has to provide them.

The deliberate "not now" items matter just as much: elasticity (traffic peaks are known in advance and load doesn't spike out of nowhere; adding capacity before a campaign is enough), multi-region operation (sales are in one country) and independently deployable parts (one team, one deployment is enough). Writing down these "no"s ties the conversation to reasons when someone proposes microservices later: which characteristic changed that reopens this decision?

Common Pitfalls

Detaching a characteristic from its domain goal. "The system must be scalable", but how many users, which peak, in how much time? Without a number there is no characteristic, only a wish. The PermitLimit = 3 in the opening was exactly a wish turned into code.

Wanting everything. A list that marks all nine characteristics as "high" tells the team nothing. If it's unclear which one wins in a conflict, the decision falls to the loudest person in the room at that moment.

Forgetting the implicit ones. The characteristics nobody writes down are the ones noticed last, usually after a production incident. Put security, recoverability and analysability on the table deliberately in the first architecture conversation.

Working from the 2011 table. The "usability" and "portability" headings copied from an old template are not wrong today, but they are incomplete: resistance, scalability and safety are missing. Use the 2023 names in a new requirements document.

Confusing a characteristic with a mechanism. "We'll use Redis" is not a characteristic, it is a solution. The characteristic is a target such as "catalog search p95 below 300 milliseconds during campaigns"; Redis is one way to reach it.

A Small Experiment

★☆☆ Change the rate limiter from the opening to partition by client IP (Internet Protocol) address using AddPolicy and RateLimitPartition.GetFixedWindowLimiter. Send the same five requests with two different partition keys and see how the output changes.

★★☆ Fill in FoSA's worksheet for the system you work on: at most seven characteristics, three of them "most important", plus the implicit ones and the ones you deliberately mark "not now". Write the ISO/IEC 25010:2023 counterpart for each.

★★★ Pick three non-functional sentences from a requirements document in your team and evaluate them against FoSA's three conditions: is it non-domain, does it influence structure, is it critical to success? Propose removing the ones that don't influence structure from the list.

In this post we named the characteristics and chose the top three for Bookstore. But "availability" and "performance" are still just words. In the next post we turn them into measurable requirements with SEI's quality attribute scenarios and see, with a real measurement, why "make it fast" can't be measured with an average.