At the end of K3, your Kladde runs in Azure: contacts, public appointment booking, tenders from the TED interface, sign-in with Entra ID, and a pipeline that builds everything from an empty subscription. You know how to build something like that. K4 asks why: Why is the Kladde cut the way it is and not some other way? What of it could you change tomorrow, and what only with weeks of work? And how does a team make decisions like these so that it still understands them a year later?
This lesson lays the groundwork. It clarifies what architecture is and what role an architect plays in the team, and it starts with what
every piece of architecture work on an existing system starts with: looking closely. At the end, your Kladde holds docs/architecture.md,
an inventory of the system as it is today.
Architecture is what is expensive to change
Ask five people what software architecture is and you'll hear five answers: the big boxes and arrows, the choice of database, the layers in the code, the cloud services. All of them have something right. For the work, a different view serves better:
The architecture of a system is the sum of its decisions that are hard to reverse.
Microsoft's guide for architects in the Well-Architected Framework says it similarly: The architecture is the collection of decisions you make, and you are responsible for each one of them and for how they work together.
What follows from that? Not every decision is architecture. Whether a method is called FindFreeSlots or GetAvailableTimes, you change
in a minute with your editor's rename. Whether the appointments live in SQL Server or in a document database, you don't change in a minute.
Data has to move, queries have to be rewritten, tests rebuilt, and operations learned from scratch. The one is craft, the other is architecture.
The line is blurry, and it moves with the system. A decision that was cheap in the first week becomes expensive as soon as data, users or other programs depend on it. The name of a method is cheap. The name of a field in a message that the worker reads is already more expensive. A name in a public address that customers have bookmarked is more expensive still.
How to recognize a heavy decision
A simple test helps: What would have to come along if we changed this in a year? The longer the list, the more it is architecture. Five things make a decision especially hard to change:
| What holds it in place | Why it gets expensive | Example in the Kladde |
|---|---|---|
| Stored data | Old data has to be migrated or kept readable. | appointments as rows in Azure SQL; times stored as UTC |
| Contracts with others | Whoever uses the interface has to follow along, often at a different time. | the messages in Kladde.Contracts; the address of the booking page |
| The cut into programs | What is delivered separately is hard to merge again, and the other way around. | web application, TED fetch and worker as three programs |
| Platform and services | Operations, permissions, monitoring and cost hang on the service. | Azure Container Apps, Service Bus, Entra ID |
| Rules that apply everywhere | Every place in the code follows them; a change hits all of them. | a person is oid and tid, never the email; business rules in Kladde.Core |
The right-hand column shows that your Kladde is full of architecture, even if you never called what you decided by that name. In K2 you decided that public pages stay static. In K3 you moved the import out of the web process into a job and a worker. Each of these decisions had reasons, and each cost something that you gave up for it.
Two thoughts go with that:
- Hard to change is not the same as wrong. Many heavy decisions are good. They just deserve more thought before you make them, and a note on why you made them that way. That is lesson 4.1.3.
- You can make decisions cheaper. If
Kladde.Coreknows no EF Core, switching the database isn't free, but it only hitsKladde.Data. Good architecture makes many decisions small by separating parts from each other. That is the topic of modules 4.2 and 4.3.
The role in the team
Architecture is a task, not necessarily a job title. In a small team, the most experienced developer often takes it on on the side; larger organizations have people who mainly do this. In both cases the same activities belong to it:
- Clarify the requirements. What should the system achieve, for whom, under what conditions? What limits do budget, deadlines, law and the team's knowledge set? An architect asks the uncomfortable questions before building, not after.
- Set quality goals. How reliable, how fast, how secure, how expensive may it be? Without these goals, no decision can be justified. That is lesson 4.1.2.
- Prepare decisions and record them. Compare options, weigh them, decide, write it down. Not alone: Whoever has to live with the consequences is asked.
- Make the system visible. Diagrams and texts with which a new colleague understands in an hour how the parts fit together. That is lesson 4.1.4.
- Check assumptions. A risky idea is tested with a small proof of concept in code before the team commits to it.
- Be there while it's being built. Read reviews, answer questions, adjust decisions when building shows something new.
The last point is the most important. Microsoft's guide explicitly warns against the architect in an ivory tower who only speaks in abstractions. An architecture that isn't in the code is a wish. That's why you keep writing code in K4, and every decision you make ends up as a pull request in the Kladde, with tests.
For work in a team, that also means: With every bigger question, say who decides. Some questions the team decides together, some the subject-matter expert (how long may booking be down?), some the person who pays. An architect who decides everything alone becomes a bottleneck. One who decides nothing leaves behind a system that nobody wanted.
Look first: the inventory
Before you change an existing system, describe how it is today. That sounds obvious, but it is often skipped, because people believe they know their own system. Writing it down then shows that the worker references a project it doesn't need, that a table is written by two places, or that nobody remembers anymore why the fetch runs at three in the morning.
An inventory answers seven questions:
- Purpose: What is the system for, and who uses it?
- Building blocks: What programs, libraries and data stores does it consist of?
- Dependencies: Who knows whom? Which projects reference which, which packages are in them?
- Data flows: What data flows from where to where, how (protocol) and when (immediately or later)?
- External systems: Who does the system talk to that doesn't belong to it?
- Operations: Where and how does it run, how does a new version get there, how do you see whether it's running?
- Known weaknesses and open questions: What already bothers us today? What does nobody know for sure?
Write down only what is, not what should be. Ideas for later go into a section of their own at the end. And write down how you know
something. “The worker scales to zero” is a claim; “the worker scales to zero (minReplicas: 0 in infra/worker.bicep)” is a finding.
The sources for an inventory of the Kladde are all in the repository:
| Question | Where to look |
|---|---|
| Building blocks | Kladde.slnx and the project files, the app host in src/Kladde.AppHost |
| Dependencies | the ProjectReference and PackageReference entries of the projects, Directory.Packages.props |
| Data flows | Program.cs of each program, the registrations of HttpClient, Service Bus and Blob Storage |
| External systems | the keys in appsettings.json (only the names, never values from operations), the sign-in, the typed clients |
| Operations | infra/, azure.yaml, the pipeline, docs/operations.md from K3 |
| Earlier decisions | docs/decisions.md from K2, the descriptions of the pull requests |
What runs in operations can differ from the code, for example a setting somebody changed in the portal. The house rules from K3 say that this must not happen. Whether they were kept is exactly one of the questions an inventory answers.
Reading the projects and their dependencies
You read the building blocks of a .NET solution first with two commands of the .NET SDK. dotnet solution list shows the solution's projects,
dotnet reference list the project references of one project:
dotnet solution list
dotnet reference list --project src/Kladde.Worker
For a solution with twelve projects, that quickly gets tedious. A small tool reads all the project files at once and writes the references
as a list and as a graph. It's a file-based app, as you know them from K1: a single .cs file that dotnet run starts without a project file. Put it
into the Kladde as tools/deps.cs:
// tools/deps.cs: lists the Kladde's projects with their references and draws them as a Mermaid graph.
// Call it in the repository's root folder: dotnet run tools/deps.cs
#:property PublishAot=false
using System.Xml.Linq;
var root = Directory.GetCurrentDirectory();
var projects = new[] { "src", "tests" }
.Where(folder => Directory.Exists(Path.Combine(root, folder)))
.SelectMany(folder => Directory
.EnumerateFiles(Path.Combine(root, folder), "*.csproj", SearchOption.AllDirectories)
.Where(file => !IsBuildOutput(Path.GetRelativePath(root, file)))
.Select(file => Read(file, folder)))
.OrderBy(project => project.Name, StringComparer.Ordinal)
.ToList();
Console.WriteLine($"{projects.Count} projects");
foreach (var project in projects)
{
Console.WriteLine();
Console.WriteLine($"{project.Name} ({project.Sdk})");
Console.WriteLine($" Projects: {List(project.Projects)}");
Console.WriteLine($" Packages: {List(project.Packages)}");
}
Console.WriteLine();
Console.WriteLine("flowchart LR");
foreach (var project in projects.Where(project => project.Folder == "src"))
{
foreach (var target in project.Projects)
{
Console.WriteLine($" {Id(project.Name)}[{project.Name}] --> {Id(target)}[{target}]");
}
}
static Project Read(string file, string folder)
{
var xml = XDocument.Load(file);
var projects = xml.Descendants("ProjectReference")
.Select(reference => ((string?)reference.Attribute("Include") ?? "").Replace('\\', '/'))
.Select(Path.GetFileNameWithoutExtension)
.OfType<string>()
.Order(StringComparer.Ordinal)
.ToList();
var packages = xml.Descendants("PackageReference")
.Select(reference => (string?)reference.Attribute("Include") ?? "")
.Order(StringComparer.Ordinal)
.ToList();
var sdk = (string?)xml.Root?.Attribute("Sdk") ?? "?";
return new Project(Path.GetFileNameWithoutExtension(file), folder, sdk, projects, packages);
}
// bin and obj contain what the build produced, not the structure of the repository.
static bool IsBuildOutput(string path) =>
path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Any(part => part is "bin" or "obj");
static string List(List<string> names) => names.Count == 0 ? "-" : string.Join(", ", names);
// A node's identifier stays one word without a dot; the text in the square brackets shows the name.
static string Id(string name) => name.Replace('.', '_');
sealed record Project(string Name, string Folder, string Sdk, List<string> Projects, List<string> Packages);
Three places deserve a second look:
#:property PublishAot=falseswitches off something that file-based apps have done on their own since .NET 10: They are prepared for Native AOT and fetch a compiler package for it. A tool that is never published doesn't need that. In the Kladde, warnings count as errors (Directory.Build.props): If NuGet reports a security vulnerability for this package, as has already happened with .NET 10, the run otherwise aborts withNU1903before the tool prints a single line.Replace('\\', '/'): Project files contain paths with the backslash, even when they are read on Linux. Only after the replacement doesPath.GetFileNameWithoutExtensionfind the name in the codespace as well.IsBuildOutputskips everything inbinandobj. Copies of project files can sit there that say nothing about the structure.
Start the tool in the Kladde's root folder:
dotnet run tools/deps.cs
For a Kladde after K3, that looks roughly like this. Your projects may be named differently, especially the TED fetch and the worker:
12 projects
Kladde.AppHost (Aspire.AppHost.Sdk/13.6.4)
Projects: Kladde.TenderFetch, Kladde.Web, Kladde.Worker
Packages: Aspire.Hosting.Azure.AppContainers, Aspire.Hosting.Azure.ServiceBus, Aspire.Hosting.Azure.Sql, Aspire.Hosting.Azure.Storage
Kladde.Contracts (Microsoft.NET.Sdk)
Projects: -
Packages: -
Kladde.Core (Microsoft.NET.Sdk)
Projects: -
Packages: -
Kladde.Core.Tests (Microsoft.NET.Sdk)
Projects: Kladde.Core
Packages: xunit.v3.mtp-v2
Kladde.Data (Microsoft.NET.Sdk)
Projects: Kladde.Core
Packages: Microsoft.EntityFrameworkCore.SqlServer
Kladde.EndToEnd (Microsoft.NET.Sdk)
Projects: -
Packages: Microsoft.Playwright, xunit.v3.mtp-v2
Kladde.ServiceDefaults (Microsoft.NET.Sdk)
Projects: -
Packages: Azure.Monitor.OpenTelemetry.AspNetCore, Microsoft.Extensions.Http.Resilience
Kladde.TenderFetch (Microsoft.NET.Sdk.Worker)
Projects: Kladde.ServiceDefaults
Packages: Aspire.Azure.Messaging.ServiceBus, Aspire.Azure.Storage.Blobs, Kladde.Contracts
Kladde.Web (Microsoft.NET.Sdk.Web)
Projects: Kladde.Core, Kladde.Data, Kladde.ServiceDefaults, Kladde.Web.Client
Packages: Microsoft.Identity.Web
Kladde.Web.Client (Microsoft.NET.Sdk.BlazorWebAssembly)
Projects: Kladde.Core
Packages: -
Kladde.Web.Tests (Microsoft.NET.Sdk)
Projects: Kladde.Web
Packages: Microsoft.AspNetCore.Mvc.Testing, xunit.v3.mtp-v2
Kladde.Worker (Microsoft.NET.Sdk.Worker)
Projects: Kladde.Core, Kladde.Data, Kladde.ServiceDefaults, Kladde.Web
Packages: Aspire.Azure.Messaging.ServiceBus, Aspire.Azure.Storage.Blobs, Kladde.Contracts
flowchart LR
Kladde_AppHost[Kladde.AppHost] --> Kladde_TenderFetch[Kladde.TenderFetch]
Kladde_AppHost[Kladde.AppHost] --> Kladde_Web[Kladde.Web]
Kladde_AppHost[Kladde.AppHost] --> Kladde_Worker[Kladde.Worker]
Kladde_Data[Kladde.Data] --> Kladde_Core[Kladde.Core]
Kladde_TenderFetch[Kladde.TenderFetch] --> Kladde_ServiceDefaults[Kladde.ServiceDefaults]
Kladde_Web[Kladde.Web] --> Kladde_Core[Kladde.Core]
Kladde_Web[Kladde.Web] --> Kladde_Data[Kladde.Data]
Kladde_Web[Kladde.Web] --> Kladde_ServiceDefaults[Kladde.ServiceDefaults]
Kladde_Web[Kladde.Web] --> Kladde_Web_Client[Kladde.Web.Client]
Kladde_Web_Client[Kladde.Web.Client] --> Kladde_Core[Kladde.Core]
Kladde_Worker[Kladde.Worker] --> Kladde_Core[Kladde.Core]
Kladde_Worker[Kladde.Worker] --> Kladde_Data[Kladde.Data]
Kladde_Worker[Kladde.Worker] --> Kladde_ServiceDefaults[Kladde.ServiceDefaults]
Kladde_Worker[Kladde.Worker] --> Kladde_Web[Kladde.Web]
The packages appear without versions because since K3 the Kladde manages its versions centrally in Directory.Packages.props. Kladde.Contracts
appears twice: as a project from which the package is built, and, for the TED fetch and the worker, among the packages, because both have taken
the contract as a package from your own feed since K3. That's why it has no arrow in the graph. You copy the lower
part, from flowchart LR on, into a code block with the language mermaid in a Markdown file. GitHub draws a picture with boxes and arrows from it.
You'll get to know Mermaid better in lesson 4.1.4.
The list is raw material, not insight. Insight comes from reading it with questions in mind:
- Does every arrow point in the allowed direction? The house rules since K2 say: Business rules live in
Kladde.Core, EF Core is an adapter. SoKladde.Datamay point toKladde.Core, never the other way around. - Does every project need what it points to? A reference that nobody uses still pulls in packages, configuration and sometimes services.
- Are there arrows between programs? A program that points to another program (not to a library) is almost always a finding.
The output above contains such a finding. You'll look for it in exercise 3.
Writing down data flows
Project references show who knows whom. They don't show what data flows where at runtime. The TED fetch doesn't know the worker, and yet every notice flows from one to the other, via Blob Storage and Service Bus. For data flows, a table with five columns helps:
| From | To | What | How | When |
|---|---|---|---|---|
| The customer's browser | Web application | desired time, name, email | HTTPS, form POST | immediately |
| Web application | Azure SQL | booking and mail in the outbox, in one transaction | TDS via EF Core | immediately |
| Web application | Microsoft Graph | confirmation mail from the outbox | HTTPS, sendMail |
seconds later, in the background |
| TED fetch | TED interface | search for new notices | HTTPS, JSON | at night, as a scheduled job |
| TED fetch | Blob Storage, Service Bus | the notice's raw data, then a reference to it | HTTPS, AMQP | right after the fetch |
| Worker | Azure SQL | tender as a row | TDS via EF Core | when the message is processed |
The When column is the most important. A flow that is “immediately” means: If the target doesn't answer, the person who is waiting right now notices. A flow that is “later” means: It can fail without anybody waiting, and it therefore needs a retry, monitoring and a receiver that tolerates duplicates. That becomes important in module 4.6.
Exercise 1
Write the inventory of your Kladde into docs/architecture.md. Use this outline:
# Architecture of the Kladde: inventory
As of: end of K3. This file describes what is, not what should be.
## Purpose and users
## Building blocks
## Dependencies
## Data flows
## External systems
## Operations
## Known weaknesses and open questions
## Ideas for later
To do so, run tools/deps.cs and take over the graph into the section “Dependencies”. Write the data flows as a table like the one above. Back
every statement about operations with the file it is in.
How to check your result: Give the file to a person who doesn't know the Kladde, or read it yourself the next day. After ten minutes of reading, can you say which programs run, where the data lives and what happens at night? And does GitHub show the graph as a picture when you open the file in the browser?
Solution 1
An inventory for the Kladde from this course. Yours will differ in names and details; what matters is that every question has an answer with evidence.
# Architecture of the Kladde: inventory
As of: end of K3. This file describes what is, not what should be.
## Purpose and users
The Kladde is the working application of a small IT office (fictional, three people). The team keeps contacts, appointments and tenders in
it; customers book an appointment through a public page. The team signs in with Entra ID, public booking needs no sign-in.
## Building blocks
| Building block | Kind | Task |
| --- | --- | --- |
| Kladde.Web | ASP.NET Core with Blazor (static SSR, one WebAssembly island) | pages, booking, sign-in, outbox |
| Kladde.TenderFetch | Container Apps job, at night | fetches new notices from TED |
| Kladde.Worker | Container App without ingress, scales via Service Bus | processes notices |
| Kladde.Core | library | business rules (free times, contacts, booking) |
| Kladde.Data | library | EF Core, migrations |
| Kladde.Contracts | package in your own feed | the import's messages |
| Azure SQL Database | data store | all business data, outbox |
| Blob Storage, container tenders-raw | data store | raw data from TED |
| Service Bus, queue tender-import | messages | references to raw data |
## Dependencies
Produced with `dotnet run tools/deps.cs`; only the projects under `src`.
```mermaid
flowchart LR
Kladde_AppHost[Kladde.AppHost] --> Kladde_TenderFetch[Kladde.TenderFetch]
Kladde_AppHost[Kladde.AppHost] --> Kladde_Web[Kladde.Web]
Kladde_AppHost[Kladde.AppHost] --> Kladde_Worker[Kladde.Worker]
Kladde_Data[Kladde.Data] --> Kladde_Core[Kladde.Core]
Kladde_TenderFetch[Kladde.TenderFetch] --> Kladde_ServiceDefaults[Kladde.ServiceDefaults]
Kladde_Web[Kladde.Web] --> Kladde_Core[Kladde.Core]
Kladde_Web[Kladde.Web] --> Kladde_Data[Kladde.Data]
Kladde_Web[Kladde.Web] --> Kladde_ServiceDefaults[Kladde.ServiceDefaults]
Kladde_Web[Kladde.Web] --> Kladde_Web_Client[Kladde.Web.Client]
Kladde_Web_Client[Kladde.Web.Client] --> Kladde_Core[Kladde.Core]
Kladde_Worker[Kladde.Worker] --> Kladde_Core[Kladde.Core]
Kladde_Worker[Kladde.Worker] --> Kladde_Data[Kladde.Data]
Kladde_Worker[Kladde.Worker] --> Kladde_ServiceDefaults[Kladde.ServiceDefaults]
Kladde_Worker[Kladde.Worker] --> Kladde_Web[Kladde.Web]
```
## Data flows
| From | To | What | How | When |
| --- | --- | --- | --- | --- |
| The customer's browser | Kladde.Web | desired time, name, email | HTTPS, form POST | immediately |
| Kladde.Web | Azure SQL | booking and mail in the outbox, one transaction | TDS via EF Core | immediately |
| Kladde.Web | Microsoft Graph | confirmation mail | HTTPS, sendMail | in the background, seconds later |
| Kladde.TenderFetch | TED interface | search for new notices | HTTPS, JSON | at night at 3 a.m. (schedule in infra/tender-fetch.bicep) |
| Kladde.TenderFetch | Blob Storage, Service Bus | raw data, then one reference per notice | HTTPS, AMQP | after the fetch |
| Kladde.Worker | Azure SQL | tender as a row, upsert by notice number | TDS via EF Core | per message |
## External systems
- Microsoft Entra ID: the team's sign-in (OpenID Connect, Microsoft.Identity.Web).
- The EU's TED interface: public tenders, no sign-in.
- Microsoft Graph: sending the confirmation mails from an Exchange Online mailbox.
## Operations
- Azure Container Apps, one environment per stage (dev, prod), region in the EU (docs/operations.md).
- Kladde.Web with two replicas (infra/web.bicep), the worker scales from zero to five (infra/worker.bicep).
- Delivery through the pipeline: pull request to dev, prod after approval (.github/workflows/deploy.yml).
- No keys: managed identities, SQL with Entra sign-in, Key Vault for the little that remains.
- Observation with Application Insights, alerts on error rate and dead-letter messages (infra/alerts.bicep).
## Known weaknesses and open questions
- Kladde.Worker references Kladde.Web (see graph). Why is not documented.
- Web and worker both write to the tenders table.
- Appointments are plain CRUD: The rule “no overlap” lives in Kladde.Core, but the page for the team checks it a second time
itself.
- Unclear: Which data may stay in the database backups for how long?
## Ideas for later
- Several offices in one Kladde.
- Quotes with approval.
Note two things about the solution. The file itself contains a code block (the Mermaid graph); so that it doesn't end the outer block in the
example, the outer one is fenced with four backticks. In your own architecture.md you don't need that. And the “Known weaknesses”
don't judge, they state. Whether a weakness gets fixed is a decision that comes later.
Exercise 2
Find three decisions from K2 or K3 in your Kladde that would be hard to change today. For each, write into a table:
- the decision in one sentence,
- when and why it was made (as far as you know),
- what would have to come along if it were changed in a year (data, contracts, programs, operations, people),
- a rough estimate: days, weeks or months.
Append the table as a section “Hard to change” to docs/architecture.md. How to check your result: Every row names at least two things in the
third column that would have to come along. If it says only “the code”, it isn't an architecture decision, and you look for
another one.
Solution 2
## Hard to change
| Decision | When and why | What would have to come along | Effort |
| --- | --- | --- | --- |
| Business data in Azure SQL with EF Core | K2: relationships and a hard lock against overlaps, the team knows SQL | move all the data, rewrite Kladde.Data, migrations, backup and restore, tests against real dependencies | weeks |
| A person is oid and tid from the token, never the email | K2: emails change, oid stays | every table with people, the sign-in, every query for “my” data | weeks |
| The import as a job, queue and worker with the contract in Kladde.Contracts | K3: two replicas ran the import twice | three programs, Bicep, the identities' permissions, alerts, the package in the feed and everyone who uses it | days to weeks |
Other good answers are: the public pages as static SSR (every page and every test of the booking would hang on it), the times as UTC in the database (all stored times would have to be converted) or Azure Container Apps as the platform (Bicep, pipeline, scaling, monitoring). A weak answer would be “the color of the buttons” or “the name of a method”: There, only the code comes along.
Exercise 3
The graph from the section “Reading the projects and their dependencies” contains an arrow that violates the questions on the list. Find it, and describe in three sentences what it means for delivery and operations. Then examine the graph of your own Kladde with the same three questions and enter what you find under “Known weaknesses”.
How to check your result: For every finding, docs/architecture.md contains the arrow (from, to) and the reason why it stands out. Whether and how
it gets fixed isn't stated there yet.
Solution 3
The arrow is Kladde.Worker --> Kladde.Web. Here a program points to another program, not to a library. What that
means:
- The worker pulls the whole web application into the build: Blazor, Microsoft.Identity.Web, the pages. Its image gets bigger, and every change to a page rebuilds the worker as well.
- At runtime, the worker can run code of the web application, such as a class that is registered in
Kladde.Weband expects configuration there that doesn't exist in the worker. That is how an error arises that occurs only in the worker. - Usually behind it is a class that both need and that ended up in
Kladde.Webbecause it was needed there first. Its right place is a library, such asKladde.CoreorKladde.Data.
As an entry in the inventory:
- Kladde.Worker references Kladde.Web (tools/deps.cs). One program hangs on another: The worker builds the whole web application
along with it and can reach its registrations. Suspected cause: a shared class lives in Kladde.Web.
In your own Kladde you'll often find similar things: a test project that references a project it doesn't test; a package in
Kladde.Core that nobody uses there; Kladde.Web.Client, the WebAssembly island, with a reference to a project that has no business
in the browser. Write everything down. It gets fixed later, with a test that keeps the error from creeping back in (module 4.3).
The Kladde after this lesson
Bring docs/architecture.md and tools/deps.cs into the Kladde as a pull request, as usual since K1: a branch, a description
with “What, why, how tested”, a review. Under “How tested” you write here that dotnet run tools/deps.cs ends with exit code 0 and
GitHub shows the graph.
The next lessons grow out of the inventory: the quality goals (4.1.2), the first ADRs (4.1.3), the C4 model (4.1.4) and the cost calculation (4.1.5). So leave the file as it is: as the state at the end of K3. Later changes to the system are described by the C4 model and the ADRs.
What you can do now
- You explain architecture as the decisions that are hard to reverse, and you recognize them by the question of what would have to come along.
- You describe what an architect does in the team, and why they write code while doing it.
- You write an inventory that says what is, and you back every statement with evidence.
- You read project references with
dotnet solution list,dotnet reference listand a tool of your own, and you find arrows that shouldn't be there. - You record data flows with protocol and timing, and you know why “later” needs more care than “immediately”.
Further reading
- Solution architect's responsibilities and guiding principles: Microsoft's description of the role, with the list of tasks and the principles this lesson builds on.
- Align technical strategy with business requirements: how to clarify requirements with the people involved before you design.
- File-based apps: all the directives of file-based apps, including
#:property PublishAot=false.