<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: mortylen</title>
    <description>The latest articles on Forem by mortylen (@mortylen).</description>
    <link>https://forem.com/mortylen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1093850%2Fc28faac3-4d9f-4349-8f07-dda703f66ed8.png</url>
      <title>Forem: mortylen</title>
      <link>https://forem.com/mortylen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mortylen"/>
    <language>en</language>
    <item>
      <title>When a Precise Specification Is Not Enough</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Wed, 15 Apr 2026 19:30:19 +0000</pubDate>
      <link>https://forem.com/mortylen/when-a-precise-specification-is-not-enough-34lm</link>
      <guid>https://forem.com/mortylen/when-a-precise-specification-is-not-enough-34lm</guid>
      <description>&lt;p&gt;When people talk about requirements for industrial software, most imagine what an operator sees: process screens, alarms, trends, production overviews, or dispatch panels. And that is largely justified. These parts of the system tend to have a relatively clear structure, as they are derived from the technology itself, process diagrams, operational habits, and well-established principles of the SCADA and HMI world.&lt;/p&gt;

&lt;p&gt;It would therefore not be accurate to say that customers in industrial environments do not know what they want to see. Operator screens are often quite predictable. They follow established logic, conventions, and expectations.&lt;/p&gt;

&lt;p&gt;But that is only the visible half of the system.&lt;/p&gt;

&lt;p&gt;The other, less visible half is much harder to define. And it is precisely there that the limitations of traditional thinking about software requirements most often become apparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Operator Sees Is Only Half of the System
&lt;/h2&gt;

&lt;p&gt;From experience, the greatest uncertainty in industrial projects does not appear on the screens seen by operators, but in the tools used behind the scenes.&lt;/p&gt;

&lt;p&gt;In more robust SCADA systems, there is usually not just a single “main” application. Alongside it, an entire ecosystem of service and administrative tools emerges. These are not displayed on the control room video wall, but without them the system would not function properly in the long run.&lt;/p&gt;

&lt;p&gt;They include, for example, historical data viewers, tools for working with measurements and data exports, database and archive management utilities, configuration of devices and communication points, alarm setup, communication diagnostics, as well as user management and audit logs.&lt;/p&gt;

&lt;p&gt;These tools are typically not seen by operators. They are used by system administrators, service technicians, process engineers, or integrators. They run on service stations, laptops, or administrative workstations. They are not visually impressive, but they are critical.&lt;/p&gt;

&lt;p&gt;Because of this, their importance is often underestimated. While the operator interface is perceived as the “real product,” service tools are often seen as just an add-on. In real operations, however, it is often the opposite.&lt;/p&gt;

&lt;p&gt;Without them, a well-designed SCADA solution quickly becomes a system that can still be operated, but is difficult to maintain, hard to extend, and unpleasant to service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Precise Requirements Are Often Missing
&lt;/h2&gt;

&lt;p&gt;This brings me to the main idea.&lt;/p&gt;

&lt;p&gt;For service and supporting applications, detailed requirements often do not exist. Not because the project is poorly managed or because the customer is not interested. The reason is simpler: customers often do not know what they will truly need when operating a complex system over time.&lt;/p&gt;

&lt;p&gt;They can define the goal. They want a system that is stable, maintainable, and extensible. They know they need historical data, configuration management, alarms, or diagnostics. But they cannot precisely describe what tools and details will actually be used in day-to-day operations.&lt;/p&gt;

&lt;p&gt;They do not know which filters will be essential when analyzing data, what will need to be changed in bulk, where exports or configuration comparisons will be required, or which information will be missing when troubleshooting issues in production. Many of these needs only become clear once the system is in use, evolving, and expanding.&lt;/p&gt;

&lt;p&gt;The customer knows the goal, but not all the tools and processes required to maintain it over time.&lt;/p&gt;

&lt;p&gt;In such situations, much of the solution is not based on a precise specification, but on trust in the team and their experience. In essence, the customer is saying: we know what the outcome should be, and we expect the vendor to design the tools that make it possible.&lt;/p&gt;

&lt;p&gt;This is not chaos. It is a natural distribution of knowledge.&lt;/p&gt;

&lt;p&gt;The customer understands the process. The vendor understands what such a system requires in practice in order to remain maintainable in the long run. And it is at the intersection of these two perspectives that tools emerge which are very difficult to specify in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Illusion of a Precise Specification
&lt;/h2&gt;

&lt;p&gt;If we try to write a fully detailed specification for these tools at the beginning of a project, the result will often look better on paper than in reality.&lt;/p&gt;

&lt;p&gt;A document is created that appears precise and professional, but in fact it only formalizes assumptions. We describe how a configuration editor should look, what filters a trend viewer should have, or how data management should work. However, the correctness of these decisions only becomes clear once someone actually starts using the tools in practice.&lt;/p&gt;

&lt;p&gt;This is where the requirements paradox emerges.&lt;/p&gt;

&lt;p&gt;The more detailed we try to describe something that has not yet been validated in practice, the more we feel that we understand it. In reality, we are only recording hypotheses with precision.&lt;/p&gt;

&lt;p&gt;This is especially visible in service tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a configuration management tool fulfills the specification but is slow in everyday use&lt;/li&gt;
&lt;li&gt;a trend viewer displays correct data but lacks the filters needed during troubleshooting&lt;/li&gt;
&lt;li&gt;a database tool is technically correct but too complex for daily operations&lt;/li&gt;
&lt;li&gt;an application allows editing individual records but lacks bulk operations&lt;/li&gt;
&lt;li&gt;a diagnostic view shows a large amount of data, but not the information that matters in critical moments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Formally, everything may be correct. In practice, the solution can still fall short.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only Usage Reveals What Is Missing
&lt;/h2&gt;

&lt;p&gt;Requirements for service tools often do not emerge at the beginning of a project, but only when the system starts being used in practice.&lt;/p&gt;

&lt;p&gt;Some needs only become visible in real operation. When a new technology is added, hundreds of tags may need to be adjusted at once, historical data must be compared, alarm configurations changed, or communication issues diagnosed. Other needs arise when the system owner changes, when the solution is expanded, or during larger interventions such as migrations or shutdowns.&lt;/p&gt;

&lt;p&gt;It is at this point that it becomes clear whether the system was designed only for presentation, or also for long-term operation.&lt;/p&gt;

&lt;p&gt;This is why industrial software should not be evaluated only by what the operator sees. Its quality is largely reflected in the tools available to the people who will maintain, modify, diagnose, and operate it over the years.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Approach in Practice
&lt;/h2&gt;

&lt;p&gt;For these parts of a system, a different approach tends to work better in practice than trying to create a perfect specification from day one.&lt;/p&gt;

&lt;p&gt;It is more effective to rely on experience from previous projects, prepare a reasonable initial design of the service tools, and present it as early as possible to the people who will actually use them.&lt;/p&gt;

&lt;p&gt;Only during real use does it become clear where users get stuck, what is unnecessarily complex, and what is missing. Based on this feedback, the design can be gradually refined, simplified, and extended.&lt;/p&gt;

&lt;p&gt;This approach is more practical than trying to design everything in advance on paper. When people see a concrete tool for managing measurements, configurations, or data, they can provide much more precise feedback.&lt;/p&gt;

&lt;p&gt;Instead of vague statements like “just make it work somehow,” they will say they need bulk operations, saved filters, configuration comparisons, export capabilities for service purposes, or audit trails of changes.&lt;/p&gt;

&lt;p&gt;At that moment, trust turns into concrete requirements. Not in a discussion over a blank sheet of paper, but through interaction with a real tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust Still Requires Rules
&lt;/h2&gt;

&lt;p&gt;The fact that detailed specifications often do not exist for these applications does not mean they should be built without structure or responsibility.&lt;/p&gt;

&lt;p&gt;Trust in the vendor is not a substitute for craftsmanship. On the contrary, it places even higher demands on the team. The developer is no longer just an executor of requirements, but a co-creator of the solution.&lt;/p&gt;

&lt;p&gt;They rely not only on current requirements, but also on experience from similar systems. They must be able to estimate which service tools will actually be needed, avoid unnecessary complexity, distinguish between a prototype and a production-ready solution, and continuously validate whether the design makes sense in real use.&lt;/p&gt;

&lt;p&gt;A good team in this context does not wait for a fully detailed specification, but continuously checks whether its assumptions hold up in real operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If I were to summarize it in one sentence: in industrial systems, customers often do not explicitly order the tools that ultimately determine whether the system can be used in the long term.&lt;/p&gt;

&lt;p&gt;Operator-facing SCADA screens are usually relatively easy to imagine and specify. The real uncertainty, however, is hidden in the background: in service applications, configuration tools, diagnostic utilities, database tools, and all the supporting parts of the system without which a large solution cannot be sustainably operated.&lt;/p&gt;

&lt;p&gt;This is precisely where detailed requirements are often missing at the start. There is a goal, there is the vendor’s experience, and there is trust that the final solution will also make sense in practice.&lt;/p&gt;

&lt;p&gt;For this reason, it may be more useful to ask not “what is the exact specification?”, but rather “how will the system behave after one, two, or five years in operation?”&lt;/p&gt;

&lt;p&gt;Because the quality of industrial software is not revealed only on the operator screen. It is revealed above all in how it feels to the people who have to maintain and operate it every day.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>SqlDependency in .NET – Query Notifications and Real-Time Data Change Reactions</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:19:49 +0000</pubDate>
      <link>https://forem.com/mortylen/sqldependency-in-net-query-notifications-and-real-time-data-change-reactions-2kkg</link>
      <guid>https://forem.com/mortylen/sqldependency-in-net-query-notifications-and-real-time-data-change-reactions-2kkg</guid>
      <description>&lt;p&gt;Imagine your application constantly bombarding the database with questions: &lt;em&gt;"Has anything changed? How about now? And now?"&lt;/em&gt; Every second, every minute. You're needlessly burning CPU, network, and database resources, even though the data might only change once an hour.&lt;/p&gt;

&lt;p&gt;There's a much better way. Let the database tell you when something changes. That's exactly what &lt;strong&gt;SqlDependency&lt;/strong&gt; is for.&lt;/p&gt;

&lt;p&gt;SqlDependency is a relatively little-known technology, yet it's a great fit for many projects that work with SQL databases. In this article, we'll look at how it works in .NET, how to set it up step by step, and how to build a simple console application that reacts to data changes in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is SqlDependency?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SqlDependency&lt;/code&gt; is a class in .NET that allows your application to receive notifications from SQL Server whenever the data you're querying changes. Under the hood, it relies on two key SQL Server mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service Broker&lt;/strong&gt; – an internal messaging system built directly into SQL Server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Notification&lt;/strong&gt; – a mechanism that monitors whether the result of a specific query has changed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle is simple: instead of repeatedly asking the database (&lt;em&gt;polling&lt;/em&gt;), you register a "subscription" for a specific query. When the result changes (INSERT, UPDATE, DELETE), SQL Server sends a notification and your application reacts to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polling vs. SqlDependency
&lt;/h3&gt;

&lt;p&gt;With traditional &lt;strong&gt;polling&lt;/strong&gt;, your application queries the database every X seconds — even when nothing has changed. This wastes CPU, network, and database resources, and the change is only detected on the next cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SqlDependency&lt;/strong&gt; works the other way around. The database itself notifies the application when a change occurs. Queries are only executed when truly needed, and the reaction is nearly instant. Instead of a &lt;em&gt;pull&lt;/em&gt; model, you switch to &lt;em&gt;push&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Put simply: polling is like constantly opening the fridge to check if new food appeared. SqlDependency is like a doorbell that rings when the delivery arrives.&lt;/p&gt;

&lt;h3&gt;
  
  
  When Not to Use SqlDependency?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When you need historical data about what changed (SqlDependency only tells you &lt;em&gt;that&lt;/em&gt; a change occurred)&lt;/li&gt;
&lt;li&gt;With very complex queries — there are syntax restrictions&lt;/li&gt;
&lt;li&gt;When periodic reads are sufficient and you don't need real-time reactions&lt;/li&gt;
&lt;li&gt;In Azure SQL, where Service Broker support is limited&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works Under the Hood
&lt;/h2&gt;

&lt;p&gt;The entire process happens in several steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The application calls &lt;code&gt;SqlDependency.Start()&lt;/code&gt; — an internal listener is created&lt;/li&gt;
&lt;li&gt;You create a &lt;code&gt;SqlCommand&lt;/code&gt; and attach a &lt;code&gt;SqlDependency&lt;/code&gt; to it&lt;/li&gt;
&lt;li&gt;You call &lt;code&gt;ExecuteNonQuery()&lt;/code&gt; or &lt;code&gt;ExecuteReader()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Data changes (INSERT, UPDATE, DELETE)&lt;/li&gt;
&lt;li&gt;SQL Server sends a notification&lt;/li&gt;
&lt;li&gt;The registration is removed — it's a one-time subscription&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What We're Going to Build
&lt;/h2&gt;

&lt;p&gt;Let's move from theory to practice. We'll create a simple console application in .NET 10 that monitors a &lt;code&gt;Messages&lt;/code&gt; table in a SQL Server database.&lt;/p&gt;

&lt;p&gt;The table will serve as a simple message queue. Each record contains a message text and a status (&lt;code&gt;New&lt;/code&gt;, &lt;code&gt;Processed&lt;/code&gt;). At startup, the application registers to watch for messages with the status &lt;code&gt;'New'&lt;/code&gt;. When a change occurs, it immediately receives a notification, prints the current state of the table, and re-registers for further changes.&lt;/p&gt;

&lt;p&gt;The entire solution consists of a single SQL script and a single &lt;code&gt;Program.cs&lt;/code&gt; file. No complex architecture — just a clean demonstration of SqlDependency in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Database (Step by Step)
&lt;/h2&gt;

&lt;p&gt;Before writing any C# code, we need to prepare the database and the environment we'll be testing with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;master&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SimpleSqlDepDB'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;SimpleSqlDepDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enabling Service Broker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;SimpleSqlDepDB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;SimpleSqlDepDB&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;ENABLE_BROKER&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;ROLLBACK&lt;/span&gt; &lt;span class="k"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; SqlDependency will not work without Service Broker enabled. This is the most common mistake. Service Broker can also be disabled after a SQL Server restart, so always verify its status.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Creating the Table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;OBJECT_ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dbo.Messages'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'U'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
    &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'New'&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inserting Test Data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Initial message'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'New'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Table and Query Requirements
&lt;/h3&gt;

&lt;p&gt;SqlDependency has strict rules about what kind of queries you can use. It's worth considering these constraints early on to make sure they fit your architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You must follow these rules:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The table must have a primary key or a unique index&lt;/li&gt;
&lt;li&gt;All columns must be explicitly listed (no &lt;code&gt;SELECT *&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Use two-part table names (&lt;code&gt;dbo.Messages&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The query must not contain:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SELECT *&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT DISTINCT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UNION&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Certain subqueries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TOP&lt;/code&gt; without &lt;code&gt;ORDER BY&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Aggregate functions (&lt;code&gt;COUNT&lt;/code&gt;, &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;AVG&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C# Implementation (.NET 10)
&lt;/h2&gt;

&lt;p&gt;Now let's look at the actual implementation. The project uses .NET 10 and the &lt;code&gt;Microsoft.Data.SqlClient&lt;/code&gt; package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.NET.Sdk"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net10.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.Data.SqlClient"&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;"7.0.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Complete Code (Program.cs)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.SqlClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Connection string – connecting to a local SQL Server Express instance&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;connStr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"Server=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MachineName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\\SQLEXPRESS;Database=SimpleSqlDepDB;Trusted_Connection=True;TrustServerCertificate=True;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// The query we'll be monitoring&lt;/span&gt;
    &lt;span class="c1"&gt;// NOTE: Must use explicit column names and a two-part table name (dbo.Messages)&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"SELECT Id, Message, Status FROM dbo.Messages WHERE Status = 'New'"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Listener started. Waiting for a value change..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. INITIALIZATION – Start the internal infrastructure for receiving notifications&lt;/span&gt;
        &lt;span class="n"&gt;SqlDependency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Print the current state of the table&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n--- Initial table data ---"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;ReadTableData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. REGISTRATION – Subscribe to data change notifications&lt;/span&gt;
        &lt;span class="nf"&gt;StartListening&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\nPress Enter to exit..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. CLEANUP – Shut down gracefully and release resources&lt;/span&gt;
        &lt;span class="n"&gt;SqlDependency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ReadTableData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SqlConnection&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SqlCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SqlDataReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasRows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"(no rows)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Formatted table output to the console&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;,-&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;,-&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;,-&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;52&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="p"&gt;],-&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Message"&lt;/span&gt;&lt;span class="p"&gt;],-&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;],-&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;StartListening&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SqlConnection&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;SqlCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Create the dependency and attach the event handler&lt;/span&gt;
        &lt;span class="n"&gt;SqlDependency&lt;/span&gt; &lt;span class="n"&gt;dependency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlDependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnChange&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;Dependency_OnChange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// ExecuteNonQuery registers the query on the SQL Server side&lt;/span&gt;
        &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQuery&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dependency_OnChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SqlNotificationEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"\n⚡ Notification — Type: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Info: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, Source: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;SqlNotificationType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Change&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n--- Table data after change ---"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nf"&gt;ReadTableData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error reading table data: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// If the type is not Change, it indicates an error or an invalid query&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Subscription error or invalid query — not re-subscribing."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// IMPORTANT: After receiving a notification, we MUST re-register!&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;StartListening&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Error re-registering listener: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Code Walkthrough
&lt;/h2&gt;

&lt;p&gt;Let's go through the key parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection String
&lt;/h3&gt;

&lt;p&gt;We're connecting to a local SQL Server Express instance using Windows authentication. Adjust the connection string to match your own environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  SqlDependency.Start() and Stop()
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Start()&lt;/code&gt; initializes the infrastructure for receiving notifications, and &lt;code&gt;Stop()&lt;/code&gt; shuts it down cleanly. Both calls are important to prevent resource leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Registering the Dependency
&lt;/h3&gt;

&lt;p&gt;By creating a &lt;code&gt;SqlDependency&lt;/code&gt; and attaching it to a &lt;code&gt;SqlCommand&lt;/code&gt;, you register the query for monitoring on the SQL Server side. The actual registration happens when the command is executed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-registration
&lt;/h3&gt;

&lt;p&gt;This is a key detail. The registration is always one-time only. After a notification is received, it's automatically removed and must be recreated.&lt;/p&gt;

&lt;h3&gt;
  
  
  SqlNotificationEventArgs
&lt;/h3&gt;

&lt;p&gt;This object contains information about the notification: type, detail, and source. A value of &lt;code&gt;Change&lt;/code&gt; means the data actually changed; other values usually indicate a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;After running the application (&lt;code&gt;dotnet run&lt;/code&gt;), you'll see the current state of the table and the application will begin waiting for changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listener started. Waiting for a value change...

--- Initial table data ---
Id    Message                        Status
----------------------------------------------------
1     Initial message                New

Press Enter to exit...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you insert a new record or modify an existing one, the notification arrives immediately and the application prints the updated data. The change is only detected if it affects the result of the monitored query.&lt;/p&gt;

&lt;p&gt;Open SQL Server Management Studio or use any other tool to send an SQL query and make a change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Insert a new record&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Test notification'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'New'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll immediately see in the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚡ Notification — Type: Change, Info: Insert, Source: Data

--- Table data after change ---
Id    Message                        Status
----------------------------------------------------
1     Initial message                New
2     Test notification              New
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try an UPDATE as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Processed'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚡ Notification — Type: Change, Info: Update, Source: Data

--- Table data after change ---
Id    Message                        Status
----------------------------------------------------
2     Test notification              New
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that after the UPDATE, the record with &lt;code&gt;Id = 1&lt;/code&gt; no longer appears, because our query only filters for records with &lt;code&gt;Status = 'New'&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service Broker is not enabled&lt;/strong&gt; — The most common issue. Verify its status and enable it if needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Incorrect query format&lt;/strong&gt; — SqlDependency is strict about syntax. If the query doesn't meet the requirements, registration will fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Forgetting to re-register&lt;/strong&gt; — Without re-subscribing, you'll only receive a single notification.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connection leaks&lt;/strong&gt; — Use &lt;code&gt;using&lt;/code&gt; blocks. Even when the connection is closed, the registration on the SQL Server side remains active.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;SqlDependency is a good fit wherever you need to react to data changes immediately.&lt;/p&gt;

&lt;p&gt;A typical example is live dashboards that only refresh when data actually changes, or simple job queues where new records trigger processing. It's also commonly used for cache invalidation — the application receives a notification and knows it's time to refresh cached data.&lt;/p&gt;

&lt;p&gt;It also has a place in IoT scenarios and simple real-time notifications where fast reactions without unnecessary polling are essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison with Alternatives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SqlDependency&lt;/strong&gt; — a simple solution built into SQL Server, but with limitations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling&lt;/strong&gt; — universal but inefficient&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SignalR&lt;/strong&gt; — great for real-time web, requires an additional layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message Queue&lt;/strong&gt; — a robust solution for distributed systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change Tracking / CDC&lt;/strong&gt; — suited for auditing and change history&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;SqlDependency is an elegant way to react to database changes without unnecessary polling. Instead of constantly querying, let SQL Server alert you only when something actually changes.&lt;/p&gt;

&lt;p&gt;Fewer queries, less load, and faster reactions. Exactly what we expect from modern applications.&lt;/p&gt;

&lt;p&gt;The complete source code for this project is available on GitHub: &lt;a href="https://github.com/mortylen/sql-dependency-notifier" rel="noopener noreferrer"&gt;github.com/mortylen/sql-dependency-notifier&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>sqlserver</category>
      <category>csharp</category>
      <category>software</category>
    </item>
    <item>
      <title>Why Simple Architecture Wins</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Tue, 07 Apr 2026 15:23:32 +0000</pubDate>
      <link>https://forem.com/mortylen/why-simple-architecture-wins-2dnp</link>
      <guid>https://forem.com/mortylen/why-simple-architecture-wins-2dnp</guid>
      <description>&lt;p&gt;There is a common misconception shared by many developers: a good architecture has to be complex. It must have layers, abstractions, patterns, and frameworks. If a system doesn’t look impressive on a whiteboard, it’s probably not “professional enough.”&lt;/p&gt;

&lt;p&gt;The truth is the opposite, and that is exactly what this article is about.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Complex systems don't appear by accident. But neither do simple ones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Simplicity Is Not the Default
&lt;/h2&gt;

&lt;p&gt;Most systems don’t start out complex. They begin as small projects with a clear goal: to solve a specific problem. Then another feature is added, and then another.&lt;/p&gt;

&lt;p&gt;Someone adds a layer “just to be tidy.” Someone else adds an abstraction “for flexibility.” A year later, no one can quickly navigate the code that two people wrote over a weekend.&lt;/p&gt;

&lt;p&gt;Complexity grows gradually. If you don’t actively manage it, it will eventually become overwhelming. Simplicity is therefore a conscious choice. Not just at the start, but throughout the system’s entire lifecycle.&lt;/p&gt;

&lt;p&gt;You have to actively protect it during the entire life of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “Simple” Really Means
&lt;/h2&gt;

&lt;p&gt;When people hear “simple architecture,” they often imagine something that isn’t what we actually mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primitive solution&lt;/strong&gt; – something thrown together quickly without much thought&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick hack&lt;/strong&gt; – code that works today but no one wants to touch tomorrow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less code&lt;/strong&gt; – short code can be just as confusing as long code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simplicity actually means something different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You can understand it quickly.&lt;/strong&gt; A new developer can get up to speed in hours, not weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changes are local.&lt;/strong&gt; When you modify something, you don’t accidentally break five other things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavior is predictable.&lt;/strong&gt; You know what will happen when you call a function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good way to understand this is by comparing it to so-called “flexible and generic” solutions. These systems are designed to handle almost anything that could come up. The problem is that they usually handle everything only moderately well and nothing particularly well.&lt;/p&gt;

&lt;p&gt;On top of that, they are hard to understand. They are full of abstractions that exist “just in case something might happen someday,” which often ends up confusing developers unnecessarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Simplicity Is Hard
&lt;/h2&gt;

&lt;p&gt;If simplicity is so good, why don’t we choose it automatically?&lt;/p&gt;

&lt;p&gt;The reason is simple. It is psychologically challenging and requires discipline and constant conscious decision-making.&lt;/p&gt;

&lt;h3&gt;
  
  
  Planning for the Future
&lt;/h3&gt;

&lt;p&gt;Developers naturally think ahead. “What if customers want more payment options?” “What if the system grows to ten times its current size?”&lt;/p&gt;

&lt;p&gt;These questions are valid. But most of them will never happen. In the meantime, we pay the price for complexity that was never needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complexity Looks Professional
&lt;/h3&gt;

&lt;p&gt;A diagram with twenty boxes and arrows looks more impressive than one with five components. When presenting your architecture to colleagues or management, a simple solution can seem like you didn’t try hard enough.&lt;/p&gt;

&lt;p&gt;This social dynamic is real and can be dangerous because developers or managers may start favoring complex solutions just because they look more professional, even when a simpler approach would work better and faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fear of Rewriting
&lt;/h3&gt;

&lt;p&gt;“What if we have to redo this later?” This is often the excuse behind unnecessary abstractions or complicated solutions.&lt;/p&gt;

&lt;p&gt;The problem is that a system filled with abstractions meant to make future changes easier usually ends up being harder to change than simple, straightforward code. Instead of making your work easier, it can make every change unnecessarily complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Following “Ideal” Examples
&lt;/h3&gt;

&lt;p&gt;We often see articles about perfect architectures, training videos, or conference presentations where everything works elegantly and sophisticatedly.&lt;/p&gt;

&lt;p&gt;It’s tempting to copy these ideal examples in your own project, creating layers, abstractions, and frameworks that look professional.&lt;/p&gt;

&lt;p&gt;The problem is that these solutions were built for very specific conditions, like large teams, millions of users, and complex processes. For a small project, they are often unnecessary and only add extra complexity.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simplicity often feels risky. Complexity feels safe, but only in the short term.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Makes a System Unnecessarily Complex
&lt;/h2&gt;

&lt;p&gt;Some problems in a system tend to repeat themselves and immediately signal unnecessary complexity. Here are the most common ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too many layers.&lt;/strong&gt; If adding a simple feature requires going through six classes and five interfaces, the system is too complicated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generic abstractions with no real use.&lt;/strong&gt; For example, a class like &lt;code&gt;AbstractBaseEntityManagerFactory&lt;/code&gt; exists, but only one part of the system actually uses it, and that part would work just fine without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solving future scenarios.&lt;/strong&gt; The system is designed for needs that don’t exist yet and may never appear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Premature splitting.&lt;/strong&gt; If a system is divided into ten separate services before anyone really understands what they do, unnecessary complexity is introduced.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you recognize any of these in your own work, you are not alone. Most of us have been there. The important part is to consciously notice where complexity accumulates and choose a better direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principles of Simple Architecture
&lt;/h2&gt;

&lt;p&gt;These guidelines help you keep systems simple and easy to manage. They are not rigid rules that must be followed every time, but rather a compass to guide your decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer explicit over generic
&lt;/h3&gt;

&lt;p&gt;Write a concrete solution for the current problem instead of creating a generic structure that claims to "handle everything." Explicit code is easy to read, while generic code often requires extra effort to understand.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Generic – hard to follow
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Explicit – clear at first glance
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;email_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solve today's problem first
&lt;/h3&gt;

&lt;p&gt;Focus on solving today's problem well. If a new problem arises later, you will handle it with better understanding. Architecture designed for an uncertain future is often just unnecessarily complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add abstractions only when needed
&lt;/h3&gt;

&lt;p&gt;Introduce abstractions only when they are truly necessary. They should emerge as a response to a real problem, not as a way to anticipate every possible future complication. If repeating code becomes difficult to manage, add an abstraction. Adding it too early creates unnecessary complexity and confusing code for both yourself and other developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the system simple
&lt;/h3&gt;

&lt;p&gt;The fewer concepts and rules a system has, the easier it is for everyone to navigate, including yourself six months from now. Every new layer, abstraction, or pattern adds cognitive load and makes the system harder to understand. Keep the system clear and minimize unnecessary parts to make working with the code simpler and faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the common case easy
&lt;/h3&gt;

&lt;p&gt;The most frequent scenarios in the system should be simple to implement and understand. If a process represents the majority of system activity, it shouldn’t require five classes or complex configuration. More complex cases and exceptions can be more complicated, because they happen less often, and that is perfectly fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Tell if a Solution is Simple Enough
&lt;/h2&gt;

&lt;p&gt;These principles provide a framework for thinking about system design. To judge whether a solution is truly simple, you can ask yourself a few practical questions. This simple checklist helps identify where unnecessary complexity is building up and what can still be simplified.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Can a new developer understand the core of the system within a few hours?&lt;/li&gt;
&lt;li&gt;[ ] Can a new feature be added without major changes to unrelated parts of the system?&lt;/li&gt;
&lt;li&gt;[ ] Does the code contain more business logic than “glue” (boilerplate, wiring, configuration)?&lt;/li&gt;
&lt;li&gt;[ ] Can you explain the architecture clearly on a single A4 page?&lt;/li&gt;
&lt;li&gt;[ ] Do you avoid giving every new teammate a 30-minute onboarding just to understand the basics of the system?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can answer “yes” to most of these, your solution is likely simple enough. If not, take a closer look at where complexity has accumulated and consider whether it is really necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: SQL vs. NewSQL
&lt;/h2&gt;

&lt;p&gt;Imagine a small project building a web application. It needs an API and a database, and the team is deciding between a traditional SQL database and a NewSQL solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why REST / Traditional SQL is Often the Better Choice
&lt;/h3&gt;

&lt;p&gt;Traditional SQL databases, such as PostgreSQL, work well for most small to medium-sized applications. They are well-documented, have a large community, and offer tools that support developers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up a local development environment is easier and faster&lt;/li&gt;
&lt;li&gt;Performance tuning and transaction management are predictable&lt;/li&gt;
&lt;li&gt;New developers can quickly understand the system&lt;/li&gt;
&lt;li&gt;Migrations and schema management are straightforward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choosing PostgreSQL from the start allows the team to focus on the business logic and solve problems that actually exist without adding unnecessary complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  When NewSQL Makes Sense
&lt;/h3&gt;

&lt;p&gt;NewSQL databases, like CockroachDB, TiDB, or PlanetScale, are powerful and scalable tools. Their power comes at a cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More complex local development environments&lt;/li&gt;
&lt;li&gt;Different transaction and consistency models&lt;/li&gt;
&lt;li&gt;A need to learn new concepts before solving the business problem&lt;/li&gt;
&lt;li&gt;More complicated migrations and schema management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These databases make sense when a project truly needs horizontal scaling or must handle a very high number of concurrent users. For smaller projects, it is usually unnecessary to introduce them right away.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Takeaway
&lt;/h3&gt;

&lt;p&gt;NewSQL is not a bad choice. It is the right solution for specific problems that most projects will never encounter. PostgreSQL can handle a massive amount of data and load, and when the time comes to scale, the team will have enough experience and data to decide whether NewSQL is truly needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Simplicity is not about laziness or avoiding thinking. On the contrary, it requires more discipline than adding extra layers, abstractions, or unnecessary generic solutions.&lt;/p&gt;

&lt;p&gt;Good architecture is neither impressive nor complicated. It should fit the problem, the team, and the stage the project is in.&lt;/p&gt;

&lt;p&gt;Every system that later appears elegant and simple was built through a series of deliberate decisions. These are decisions about what not to add, what not to solve in advance, and what can be safely addressed later.&lt;/p&gt;

&lt;p&gt;Before making any major architectural decision, it’s worth asking yourself one simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Are we making this simpler, or just more sophisticated?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;em&gt;Explore practical tips for architectural decisions at &lt;a href="http://stackcompassguide.dev" rel="noopener noreferrer"&gt;Stack Compass Guide&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>When Good Intentions Become a Problem: Overengineering</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Mon, 30 Mar 2026 15:36:19 +0000</pubDate>
      <link>https://forem.com/mortylen/when-good-intentions-become-a-problem-overengineering-1hm6</link>
      <guid>https://forem.com/mortylen/when-good-intentions-become-a-problem-overengineering-1hm6</guid>
      <description>&lt;p&gt;Many software systems are not problematic because they are too simple. The problem often arises when they are unnecessarily complex.&lt;/p&gt;

&lt;p&gt;When a system is too complicated, it becomes harder to navigate, changes take longer, and bugs are more difficult to find. The result is slower development and more stress in day-to-day work.&lt;/p&gt;

&lt;p&gt;This complexity usually doesn’t appear all at once. It builds up gradually through decisions that initially make sense. The goal is to be prepared for growth, maintain flexibility, and avoid costly changes in the future.&lt;/p&gt;

&lt;p&gt;The problem begins when future problems are addressed too early.&lt;/p&gt;

&lt;p&gt;That is what we often call overengineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is overengineering
&lt;/h2&gt;

&lt;p&gt;Overengineering means that a system is more complex than it currently needs to be.&lt;/p&gt;

&lt;p&gt;It’s not about a specific technology being bad on its own. The problem arises when it is used too early or without a clear reason.&lt;/p&gt;

&lt;p&gt;Simply put:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more layers are added than necessary,&lt;/li&gt;
&lt;li&gt;more abstractions are created than are actually used,&lt;/li&gt;
&lt;li&gt;we prepare for problems that don’t exist yet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Such an architecture may look professional at first glance. At the same time, it introduces immediate costs. The system becomes harder to understand, harder to change, and more difficult to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Overengineering is rarely intentional. On the contrary, the goal is usually to do things properly. We want to avoid future problems, prepare the system for growth, and feel like we are building a solid solution.&lt;/p&gt;

&lt;p&gt;These are perfectly reasonable goals. The problem is that the future in software is hard to predict. What seems like good preparation today may turn out to be unnecessary in a few months.&lt;/p&gt;

&lt;p&gt;Another reason is inspiration from large companies. When we read how tech giants build systems, it’s easy to feel like we need a similar architecture. But large companies are solving very different problems than a smaller project or a new product.&lt;/p&gt;

&lt;p&gt;There is also an aesthetic aspect. A simple solution can feel ordinary, while a complex one looks more advanced. That doesn’t mean it is better.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a good pattern becomes an anti-pattern
&lt;/h2&gt;

&lt;p&gt;Many software patterns are useful. Microservices, CQRS, layered architecture, or GraphQL are not bad on their own.&lt;/p&gt;

&lt;p&gt;The problem arises when they are used at the wrong time.&lt;/p&gt;

&lt;p&gt;A pattern can become an anti-pattern when its costs are high today, its benefits remain theoretical, and the project doesn’t actually have a problem that the solution is meant to solve.&lt;/p&gt;

&lt;p&gt;In other words, a pattern becomes an anti-pattern when it is unnecessarily expensive and unnecessarily complex for a given project.&lt;/p&gt;

&lt;p&gt;This often happens subtly. The decision is justified with arguments like “we might need it later” or “this is the proper way to do it.” But without a concrete problem, it’s more of a hypothesis than a real need.&lt;/p&gt;

&lt;p&gt;It’s important to realize that most patterns were created as a response to specific problems. If we don’t have those problems, we probably don’t need the solution either.&lt;/p&gt;

&lt;p&gt;A better approach is to introduce patterns gradually—at the moment when they start solving a real, recurring problem. At that point, it’s no longer a guess, but a response to actual experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common forms of overengineering
&lt;/h2&gt;

&lt;p&gt;One of the most common situations is preparing for scale that doesn’t exist yet. The system is designed as if it already had a massive number of users, even though the product is still in its early stages.&lt;/p&gt;

&lt;p&gt;Another issue is premature abstractions. A large number of interfaces, base classes, or generic solutions are created before there are multiple real use cases.&lt;/p&gt;

&lt;p&gt;A frequent problem is also excessive flexibility. The system is designed to handle almost anything, but everyday work with it becomes unnecessarily complicated.&lt;/p&gt;

&lt;p&gt;And finally, premature decomposition. The application is split into multiple parts before there are clear boundaries and a concrete reason to do so.&lt;/p&gt;

&lt;p&gt;In all of these cases, complexity is added before there is any real benefit from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: REST API vs. GraphQL
&lt;/h2&gt;

&lt;p&gt;Let’s imagine a smaller project building a web application. It needs an API for the frontend and is deciding between a REST API and GraphQL.&lt;/p&gt;

&lt;p&gt;A REST API is usually the simpler starting point. It has clear endpoints, is easy to explain, and straightforward to work with. For a smaller application, it is often more than enough.&lt;/p&gt;

&lt;p&gt;GraphQL can be very useful, especially when there are multiple clients with different data needs or complex screens that compose data from multiple sources. The client can request exactly what it needs.&lt;/p&gt;

&lt;p&gt;That doesn’t mean GraphQL is automatically better.&lt;/p&gt;

&lt;p&gt;If the application is simple, has a single frontend, and standard data requirements, GraphQL can introduce more complexity than value. You need to deal with schema design, resolvers, security, caching, and other concerns that are much more straightforward with a simple REST API.&lt;/p&gt;

&lt;p&gt;In such a case, adopting GraphQL just because it feels more modern would be unnecessary.&lt;/p&gt;

&lt;p&gt;On the other hand, as the application grows, data requirements become more complex, and the REST API starts to feel limiting, GraphQL can start to make sense.&lt;/p&gt;

&lt;p&gt;Don’t choose technology based on what sounds more advanced. Choose it based on what solves your current problem in the simplest way.&lt;/p&gt;

&lt;h2&gt;
  
  
  A better approach
&lt;/h2&gt;

&lt;p&gt;Instead of adding complexity upfront, it’s better to start simple and introduce new layers only when there is a clear reason.&lt;/p&gt;

&lt;p&gt;It helps to ask a few simple questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What problem are we solving right now?&lt;/li&gt;
&lt;li&gt;Is it a real problem we have, or something we assume might happen?&lt;/li&gt;
&lt;li&gt;Would a simpler solution be enough for now?&lt;/li&gt;
&lt;li&gt;How much complexity are we adding?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach doesn’t mean ignoring the future. It just means not paying the cost of complexity before it is actually needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Overengineering is dangerous because it often looks reasonable at the beginning.&lt;/p&gt;

&lt;p&gt;It feels like thoroughness, preparedness, and solid design. In reality, it can slow down development and make the system unnecessarily hard to understand and maintain.&lt;/p&gt;

&lt;p&gt;Good architecture doesn’t have to be complex. It should be appropriate for what the product needs today.&lt;/p&gt;

&lt;p&gt;Before making a bigger technical decision, it’s worth asking a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are we solving a real problem, or just creating future complexity?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;em&gt;Explore practical tips for architectural decisions at &lt;a href="http://stackcompassguide.dev" rel="noopener noreferrer"&gt;Stack Compass Guide&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>overengineering</category>
      <category>cleancode</category>
      <category>architecture</category>
    </item>
    <item>
      <title>When Code Hurts: Anti-Patterns in Software Development</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Mon, 23 Mar 2026 18:20:33 +0000</pubDate>
      <link>https://forem.com/mortylen/when-code-hurts-anti-patterns-in-software-development-4424</link>
      <guid>https://forem.com/mortylen/when-code-hurts-anti-patterns-in-software-development-4424</guid>
      <description>&lt;p&gt;Imagine building a house without a proper plan. At first, everything moves fast. The foundations are done, the walls go up, the roof is finished sooner than expected. Every day you see progress, and it pushes you forward.&lt;/p&gt;

&lt;p&gt;But gradually, problems start to appear. Pipes run through places where load-bearing walls should be. Electrical wiring is laid out in a way that makes it inaccessible without tearing things apart. If you want to add a window, you end up interfering with the structure of the entire building.&lt;/p&gt;

&lt;p&gt;Suddenly, you are no longer building. You are just fixing previous decisions. Every small change costs more time, energy, and money than the construction itself.&lt;/p&gt;

&lt;p&gt;This is exactly how anti-patterns emerge in software. The application runs, the user interface works, and the deadline is met. Not because of one big mistake, but because of a series of small compromises that gradually turn into a system that is hard to change. Every small modification becomes complicated, new bugs appear, and development slows down. The common denominator in these situations is &lt;strong&gt;anti-patterns&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An anti-pattern is a “bad solution that looks like a good one.” It is not intentional sabotage, but a decision that makes sense in the moment while causing more harm than good in the long run.&lt;/p&gt;

&lt;p&gt;It is also important to understand that an anti-pattern is not the same as a bug. A bug means the code does not work correctly, while an anti-pattern describes code that works but is poorly designed, with problems that only become visible over time when the code needs to be changed or extended.&lt;/p&gt;

&lt;p&gt;It is also worth noting that anti-patterns are not just made by beginners. Everyone makes them, especially under pressure, without enough context, or simply because the fastest solution is not always the best one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Happens
&lt;/h2&gt;

&lt;p&gt;The most common trigger for anti-patterns is time. Every developer knows the phrase &lt;em&gt;“We need this by tomorrow.”&lt;/em&gt; When a deadline is looming, few people think about ideal architecture or elegant solutions. The goal is simple: make the code work and meet the deadline.&lt;/p&gt;

&lt;p&gt;The problem is that these “temporary solutions” very easily become permanent. What started as a small shortcut gradually accumulates, and the code becomes harder to maintain.&lt;/p&gt;

&lt;p&gt;Changing requirements make things even worse. The project keeps expanding, and new tasks appear, like &lt;em&gt;“just add this”&lt;/em&gt;, &lt;em&gt;“tweak it a bit”&lt;/em&gt;, or &lt;em&gt;“let’s change the logic.”&lt;/em&gt; The code starts piling up without a clear plan, and what was once a clean application slowly turns into chaos.&lt;/p&gt;

&lt;p&gt;Later, when a new developer joins the project, they inherit this unstructured code, and the anti-patterns continue to compound. That is exactly the moment when every change starts to “hurt,” and even small modifications can cause unexpected issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Important Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;Software development is full of pitfalls, and some mistakes happen so often that they have their own names. The following examples are among the most common and show how a seemingly small decision can gradually complicate an entire project.&lt;/p&gt;

&lt;h3&gt;
  
  
  God Object – When One Class Knows Everything
&lt;/h3&gt;

&lt;p&gt;This is one of the most widespread problems. A single class or file gradually starts taking on more and more responsibilities. For example, it validates data, communicates with the database, sends emails, and logs events.&lt;/p&gt;

&lt;p&gt;At first, it feels convenient because everything is in one place. Later, it turns into a nightmare, because any change can break something else and testing becomes almost impossible.&lt;/p&gt;

&lt;p&gt;A simple rule: &lt;strong&gt;one class, one responsibility.&lt;/strong&gt;&lt;br&gt;
If you need to use the word “and” when describing a class, it is probably doing too many things.&lt;/p&gt;
&lt;h3&gt;
  
  
  Spaghetti Code – Code Without Structure
&lt;/h3&gt;

&lt;p&gt;The result is deeply nested conditions, function calls from unpredictable places, and no clear flow of logic. It emerges gradually when new functionality is simply “glued” onto existing code instead of thinking through the structure.&lt;/p&gt;

&lt;p&gt;The best indicator that code is problematic? When you hear someone on the team say: &lt;em&gt;“Better not touch that.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The following example shows how code can work while still being almost unreadable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;# more logic...
&lt;/span&gt;                        &lt;span class="k"&gt;pass&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# another branch...
&lt;/span&gt;        &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without carefully reading every line, it is hard to understand what is going on. The solution is to break the logic into well-named functions, each doing one thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Copy-Paste Programming – Duplication That Backfires
&lt;/h3&gt;

&lt;p&gt;Copying a block of code into multiple places and slightly modifying it may look like a quick solution. In practice, it means that when you find a bug, you have to fix it in several places. It is easy to miss one.&lt;/p&gt;

&lt;p&gt;Shared logic belongs in a function or a common module, not scattered across the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Magic Numbers – Numbers Without Context
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why 18? A legal requirement? An internal rule? A test value? The code does not make it clear.&lt;/p&gt;

&lt;p&gt;A small change makes a big difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;LEGAL_AGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LEGAL_AGE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single named constant significantly improves readability, and changing the value becomes a one-line update.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tight Coupling – When Everything Depends on Everything
&lt;/h3&gt;

&lt;p&gt;Coupling describes how interconnected parts of a system are. Some level of dependency is inevitable, but the problem arises when there is too much.&lt;/p&gt;

&lt;p&gt;If a change in one part of the system breaks other parts, the components are too tightly coupled.&lt;/p&gt;

&lt;p&gt;This makes every modification complicated and turns expanding the application into a risky adventure. The goal is for each part of the system to be as independent as possible. Such components are easier to test, modify, and replace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lava Flow – Old Code Everyone Fears
&lt;/h3&gt;

&lt;p&gt;The name comes from an analogy. &lt;em&gt;Old lava hardens and becomes a permanent part of the landscape&lt;/em&gt; – removing it is almost impossible.&lt;/p&gt;

&lt;p&gt;Similarly, old parts of the code accumulate, and no one really knows what they do. There are no tests for them, and no one wants to touch them for fear of breaking something.&lt;/p&gt;

&lt;p&gt;The result? This code stays in the project for years, and developers prefer to write workarounds around it rather than understand what’s inside.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Example
&lt;/h2&gt;

&lt;p&gt;Imagine you are tasked with writing user registration. A quick solution might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Email is required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;save_to_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User created:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works. Deadline met.&lt;/p&gt;

&lt;p&gt;The problem arises when you need to add SMS notifications, change validation, save to a different database, or add an audit log. The class starts growing, readability drops, and the risk of errors increases.&lt;/p&gt;

&lt;p&gt;A better approach separates responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserValidator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# validation logic goes here
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# database operations go here
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotificationService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# email sending logic goes here
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notifier&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each class has a single responsibility. If you change the way emails are sent, you only modify &lt;code&gt;NotificationService&lt;/code&gt; and the rest of the system remains untouched.&lt;/p&gt;

&lt;p&gt;Notice also that anti-patterns often appear in combination. &lt;code&gt;UserManager&lt;/code&gt; from the first example is not only a &lt;strong&gt;God Object&lt;/strong&gt;, but also the beginning of &lt;strong&gt;Spaghetti Code&lt;/strong&gt; and &lt;strong&gt;Tight Coupling&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Most real-world code problems are a mix of several anti-patterns at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Avoid Anti-Patterns
&lt;/h2&gt;

&lt;p&gt;You don’t need to be a senior architect to avoid the most common anti-patterns. A few simple habits can improve code quality and make maintenance easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Separate Responsibilities
&lt;/h3&gt;

&lt;p&gt;If you feel that a class or function is doing too many things, it probably is. Strive to give each part of the code a clear role. Validation, database operations, and sending notifications should be separated. This makes the code easier to test and modify without breaking anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Refactor Continuously
&lt;/h3&gt;

&lt;p&gt;Refactoring is not a big system rewrite, but a series of small steps done regularly. Rename variables, split large functions, remove duplication, and gradually improve code clarity. Small daily improvements accumulate over time, turning chaos into a clear structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Write Tests
&lt;/h3&gt;

&lt;p&gt;Even simple tests greatly reduce the risk of breaking something when making changes. Testing also helps you understand the code’s logic better and catch inconsistencies before they reach production. Unit tests, even basic ones, keep the project under control and give you confidence during refactoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Keep It Simple
&lt;/h3&gt;

&lt;p&gt;The simplest solution is often the best. Don’t try to optimize “for the future” unless there is a specific reason. Code should first and foremost be readable and understandable. Make it work first, and optimize later if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Review Your Own Code
&lt;/h3&gt;

&lt;p&gt;A short self code review a few hours after writing code can reveal things you missed. Imagine someone new reading it—if it doesn’t make sense, fix it immediately. This simple habit improves clarity and uncovers hidden issues before they spread.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Gradually Improve Existing Code
&lt;/h3&gt;

&lt;p&gt;If you join a project with existing anti-patterns, don’t rewrite everything at once. Identify the most critical areas and improve them gradually, always with tests. Small steps are more effective than radical changes, and even partial order is better than chaos.&lt;/p&gt;

&lt;p&gt;By following these habits, your projects will become more organized, less prone to anti-patterns, and easier to work with for you and your team. The goal is not perfection, but &lt;strong&gt;continuous improvement and quality control&lt;/strong&gt;, making code more readable and easier to modify.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;Anti-patterns are a natural part of software development. You can’t avoid them completely, nor should you try. Every developer encounters them. The difference between a beginner and an experienced developer is not that they never create them, but that they can recognize and gradually eliminate them.&lt;/p&gt;

&lt;p&gt;Code is never truly finished. Good projects aren’t created perfectly on the first try. They grow over time through writing, testing, and improving.&lt;/p&gt;

&lt;p&gt;It’s enough to regularly ask yourself a few simple questions:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Is this solution understandable? Could someone else modify it? Will it still work smoothly a month from now?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the answer is yes to all of them, you’re on the right track.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;em&gt;Explore practical tips for architectural decisions at &lt;a href="http://stackcompassguide.dev" rel="noopener noreferrer"&gt;Stack Compass Guide&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>cleancode</category>
      <category>programming</category>
      <category>architecture</category>
      <category>development</category>
    </item>
    <item>
      <title>Most Software Architecture Decisions Are Actually About Trade-offs</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Wed, 18 Mar 2026 09:38:57 +0000</pubDate>
      <link>https://forem.com/mortylen/most-software-architecture-decisions-are-actually-about-trade-offs-1413</link>
      <guid>https://forem.com/mortylen/most-software-architecture-decisions-are-actually-about-trade-offs-1413</guid>
      <description>&lt;p&gt;Development teams rarely struggle with having too few options. Much more often, they face the opposite problem: there are &lt;strong&gt;too many&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Today, almost every part of a system can be built in multiple ways. You can build a monolith or split your application into microservices. You can use REST APIs or GraphQL. You can store data in SQL or NoSQL databases. You can keep operations simple with Docker Compose or move to Kubernetes.&lt;/p&gt;

&lt;p&gt;At first glance, this sounds like an advantage. In practice, however, it often leads to confusion and lengthy debates. The hardest part is usually not discovering new technologies. The hardest part is choosing between them without the team falling into hype, personal preferences, or the fear of making the “wrong” decision.&lt;/p&gt;

&lt;p&gt;That’s why most software architecture decisions are not really about technology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are decisions about trade-offs between simplicity, speed, cost, and risk.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem: Architecture Decisions Are Often Intuitive Rather Than Systematic
&lt;/h2&gt;

&lt;p&gt;In many teams, architecture decisions don’t happen in complete chaos—but they aren’t fully systematic either. They usually emerge as a mix of time, team experience, existing stack, current project pain points, and business pressure.&lt;/p&gt;

&lt;p&gt;In practice, it often looks like this:&lt;/p&gt;

&lt;p&gt;Someone proposes a technology because they already know it, have read about it, or successfully used it in another project.&lt;/p&gt;

&lt;p&gt;The team starts weighing pros and cons, but the discussion quickly shifts from real needs to personal preferences.&lt;/p&gt;

&lt;p&gt;Several other factors also influence the decision: deadlines, budget, existing infrastructure, the team’s ability to maintain a new technology, client requirements, security rules, hiring, and sometimes even internal politics.&lt;/p&gt;

&lt;p&gt;In the end, a choice is made not because the team found the objectively “best” option, but because one option seems the most acceptable at the moment.&lt;/p&gt;

&lt;p&gt;That alone isn’t the problem. The problem arises when the real reasons behind a decision remain implicit and unspoken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The team may think it’s deciding on technology, but it’s actually deciding on something else:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speed of delivery,
&lt;/li&gt;
&lt;li&gt;operational complexity,
&lt;/li&gt;
&lt;li&gt;what the team already knows vs. what it still needs to learn,
&lt;/li&gt;
&lt;li&gt;the level of risk the team is willing to accept,
&lt;/li&gt;
&lt;li&gt;whether today’s simplicity or future migration will hurt more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When these factors aren’t explicitly named, predictable patterns emerge. Opinions start to outweigh facts. Discussions turn into defending favorite tools. Trade-offs remain hidden. The team ends up optimizing for novelty, status, or hypothetical future scenarios instead of current needs.&lt;/p&gt;

&lt;p&gt;The result is rarely a “bad” technical solution in an absolute sense. More often, it’s a solution that is too costly, too complex, or simply mismatched to the team’s and product’s current situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is exactly how architecture can look convincing on a diagram, yet in daily practice it brings unnecessary costs, slows development, and increases operational overhead.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Most Technologies Aren’t “Better” – They Just Optimize Different Trade-offs
&lt;/h2&gt;

&lt;p&gt;One of the most useful mindset shifts in thinking about architecture is this: &lt;strong&gt;most technologies aren’t universally better than their alternatives.&lt;/strong&gt; They are simply better suited for specific constraints, priorities, and situations.&lt;/p&gt;

&lt;p&gt;In other words: &lt;strong&gt;architecture decisions aren’t about finding the best technology.&lt;br&gt;&lt;br&gt;
They’re about choosing the right trade-offs for a particular context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few examples make this clear.&lt;/p&gt;

&lt;p&gt;Monolith vs. microservices isn’t a debate between simplicity and sophistication. It’s usually a trade-off between simplicity and speed of delivery on one side, and independent scalability, greater team autonomy, and higher operational complexity on the other.&lt;/p&gt;

&lt;p&gt;Kubernetes vs. Docker Compose isn’t enterprise vs. amateur tooling. It’s a trade-off between operational power and large-scale automation on one hand, and lower setup and maintenance complexity on the other.&lt;/p&gt;

&lt;p&gt;A technology can be excellent and still be the wrong choice for a given project.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The wrong technology is often just the right technology used in the wrong context.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Framework for Architecture Decisions
&lt;/h2&gt;

&lt;p&gt;If architecture is mostly about trade-offs, teams need a simple way to make those decisions. It doesn’t have to be complicated. In fact, it works better when it’s kept as simple as possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here’s a straightforward, practical framework:&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Your Constraints
&lt;/h3&gt;

&lt;p&gt;Before diving into specific technologies, clarify the environment in which the decision will operate.&lt;/p&gt;

&lt;p&gt;Helpful questions include:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How big is the team?
&lt;/li&gt;
&lt;li&gt;What are the team’s current strengths?
&lt;/li&gt;
&lt;li&gt;How much time and budget do you have?
&lt;/li&gt;
&lt;li&gt;What are the real requirements for reliability and performance?
&lt;/li&gt;
&lt;li&gt;How mature is your infrastructure (deployment, monitoring, processes)?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small startup and a large platform company have completely different constraints. Using the same architecture for both is almost always a mistake.&lt;/p&gt;

&lt;p&gt;A five-person startup building an MVP faces very different constraints than a platform team running multiple products in production. If both teams copy the same architecture, one of them is likely making the wrong choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Make Trade-offs Explicit
&lt;/h3&gt;

&lt;p&gt;Once you understand your constraints, make the trade-offs visible.&lt;/p&gt;

&lt;p&gt;For each option, ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What do we gain?
&lt;/li&gt;
&lt;li&gt;What do we pay for it?
&lt;/li&gt;
&lt;li&gt;What complexity does this introduce?
&lt;/li&gt;
&lt;li&gt;What risks are we accepting?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trade-offs aren’t a side effect of a decision.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Trade-offs are the decision itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example, moving to distributed services may improve isolation and scalability, but it also brings network failures, more complex deployments, higher observability demands, and greater coordination costs. These aren’t side notes—they are an inherent part of the decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Ask the Right Questions
&lt;/h3&gt;

&lt;p&gt;Good decisions don’t come from strong opinions—they come from the right questions.&lt;/p&gt;

&lt;p&gt;For example, instead of asking, “Should we use microservices?”  &lt;/p&gt;

&lt;p&gt;Ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we need independent scaling right now?
&lt;/li&gt;
&lt;li&gt;Do we have multiple teams that need to deploy independently?
&lt;/li&gt;
&lt;li&gt;Can our team handle the increased complexity?
&lt;/li&gt;
&lt;li&gt;Are the system boundaries stable enough?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right questions force the team to think about reality, not trends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Favor the Simplest Solution That Works
&lt;/h3&gt;

&lt;p&gt;This is the step teams most often skip.&lt;/p&gt;

&lt;p&gt;In software development, teams frequently solve problems too early. They think about large-scale scaling before they have many users, complex deployments before the system is properly modularized, and heavy infrastructure before they have people who can manage it.&lt;/p&gt;

&lt;p&gt;A better approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose the simplest solution that meets your current needs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This doesn’t mean ignoring the future. It means avoiding paying for complexity before it’s truly necessary.&lt;/p&gt;

&lt;p&gt;The goal is to add complexity only when its need is clearly demonstrated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This framework isn’t perfect or universal. But it helps teams make decisions consciously, rather than based on impressions, trends, or chance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Example: Monolith vs. Microservices
&lt;/h2&gt;

&lt;p&gt;Deciding between a monolith and microservices is one of the best examples of thinking in terms of trade-offs.&lt;/p&gt;

&lt;p&gt;Imagine a small team building a product in a changing domain. Requirements are still evolving. Functionality is added gradually. System boundaries aren’t fully stable. The business primarily pressures for speed of delivery.&lt;/p&gt;

&lt;p&gt;How would decision-making look in this situation?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, clarify your constraints.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How many developers are on the team? How often do you deploy? How painful are your current releases? Do you already have solid monitoring, tracing, CI/CD, and incident response processes?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next, make the trade-offs explicit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A monolith usually brings faster local development, simpler deployment, easier debugging, and lower operational costs. Microservices can provide better service isolation, independent scaling, and clearer ownership boundaries—but only if the organization can handle the additional complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally, ask the decisive questions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Do you truly need independent scaling right now? Do multiple teams need to deploy independently? Does your DevOps maturity support managing many moving parts? Are the service boundaries stable enough for splitting the system to reduce coupling rather than just moving it across the network?&lt;/p&gt;

&lt;p&gt;If the answer to most of these questions is &lt;strong&gt;no&lt;/strong&gt;, a modular monolith is usually the better starting point.&lt;/p&gt;

&lt;p&gt;If the answer to most is &lt;strong&gt;yes&lt;/strong&gt;, microservices may make sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The decision should be driven by real system pressures, not architectural fashion.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with a monolith, modularize early, and split into services only when the need is proven.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Still Overcomplicate Architecture
&lt;/h2&gt;

&lt;p&gt;If all of this sounds reasonable, why do teams still end up with unnecessarily complex architectures?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because architecture decisions aren’t just technical—they’re also human.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Trend Pressure
&lt;/h3&gt;

&lt;p&gt;Companies like Netflix, Amazon, or Uber use a particular approach, which can create the feeling that it’s the “right” way.&lt;br&gt;&lt;br&gt;
But large companies solve large-company problems.&lt;br&gt;&lt;br&gt;
Copying their architecture without their scale, teams, and infrastructure is a common mistake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resume-Driven Development
&lt;/h3&gt;

&lt;p&gt;Some decisions aren’t driven by product needs, but by a desire to work with modern technologies.&lt;br&gt;&lt;br&gt;
The system then ends up optimized more for a résumé than for reality.&lt;/p&gt;

&lt;p&gt;The result: more complexity, less value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Premature Scaling
&lt;/h3&gt;

&lt;p&gt;Teams often design architecture for problems they don’t yet have:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;high traffic,
&lt;/li&gt;
&lt;li&gt;many teams,
&lt;/li&gt;
&lt;li&gt;extreme complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The costs are immediate. The benefits may never materialize.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Possibility ≠ Business Need
&lt;/h3&gt;

&lt;p&gt;Just because a system &lt;em&gt;can&lt;/em&gt; be more distributed, flexible, or abstract doesn’t mean it &lt;em&gt;should&lt;/em&gt; be.&lt;/p&gt;

&lt;p&gt;In most cases, the problem isn’t the technology.&lt;br&gt;&lt;br&gt;
The problem is optimizing for the wrong things.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Approach: Turn Decisions into Questions
&lt;/h2&gt;

&lt;p&gt;One of the simplest improvements a team can make:&lt;/p&gt;

&lt;p&gt;Stop debating technologies directly and start turning decisions into questions.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Should we use GraphQL?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we have many clients with different data needs?
&lt;/li&gt;
&lt;li&gt;Are fixed REST responses inefficient for us?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Should we move to Kubernetes?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we really need cluster orchestration?
&lt;/li&gt;
&lt;li&gt;Do we need self-healing and advanced automation?
&lt;/li&gt;
&lt;li&gt;Does the added operational complexity justify itself?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Should we use NoSQL?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are our data models unsuitable for a relational approach?
&lt;/li&gt;
&lt;li&gt;Do we have scaling requirements that SQL cannot handle?
&lt;/li&gt;
&lt;li&gt;Do we need a flexible schema?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach works because questions reveal hidden assumptions. They don’t eliminate uncertainty, but they make decisions more conscious and defensible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good questions don’t lead to perfect answers—they lead to better decisions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Most software architecture decisions aren’t a battle between good and bad technologies.&lt;/strong&gt; They are a choice between trade-offs.&lt;/p&gt;

&lt;p&gt;The goal isn’t to find a universally “best” tool. The goal is to choose a solution that fits your team, your constraints, and your product’s current stage.&lt;/p&gt;

&lt;p&gt;To make better architectural decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define your constraints,
&lt;/li&gt;
&lt;li&gt;make trade-offs explicit,
&lt;/li&gt;
&lt;li&gt;ask the right questions,
&lt;/li&gt;
&lt;li&gt;choose the simplest solution that works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good architecture isn’t the most modern. It’s the one that makes sense in the given context.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;em&gt;Explore practical tips for architectural decisions at &lt;a href="http://stackcompassguide.dev" rel="noopener noreferrer"&gt;Stack Compass Guide&lt;/a&gt;.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@jibinsamuelphotography?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;JIBIN SAMUEL&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>k-NN Classification and Model Evaluation</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Tue, 26 Aug 2025 07:19:29 +0000</pubDate>
      <link>https://forem.com/mortylen/k-nn-classification-and-model-evaluation-2of7</link>
      <guid>https://forem.com/mortylen/k-nn-classification-and-model-evaluation-2of7</guid>
      <description>&lt;p&gt;In this article, I focus on selecting evaluation metrics such as Accuracy, Precision, Recall, and F1-Score, and I will try to explain in which situations each of them is appropriate to use. We will also see how to implement k-NN in the Rust programming language. This article is intended for readers who want to understand how to evaluate models for simple classification in machine learning, and I will also outline how the k-NN algorithm works. I chose the k-NN algorithm for its simplicity and ease of understanding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to k-NN Classification
&lt;/h2&gt;

&lt;p&gt;The goal of classification algorithms is to predict the category or class to which a given object belongs, based on historical data.&lt;/p&gt;

&lt;p&gt;A typical example is predicting whether an email is spam or not, whether a credit card is fraudulent, or whether a programming project will be successful based on various factors such as the programmer’s experience, technological difficulty, or the number of cups of coffee they drink throughout the day.&lt;/p&gt;

&lt;h3&gt;
  
  
  k-NN (k-Nearest Neighbors) Algorithm
&lt;/h3&gt;

&lt;p&gt;One of the simplest and most intuitive classification algorithms is k-NN (k-Nearest Neighbors), which belongs to the family of supervised learning algorithms. This algorithm is based on the idea that objects (or data points) within the same or similar categories will be closer to each other in space.&lt;/p&gt;

&lt;p&gt;k-NN works by, for each new unlabeled object (e.g., a project), finding the "k" nearest neighbors in the training set, and based on their category (e.g., successful/unsuccessful project), it assigns the new object to the same category. The advantage is the simplicity of implementation and intuitive understanding. A disadvantage can be the higher computational cost with large datasets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Preparation for k-NN Classification
&lt;/h2&gt;

&lt;p&gt;Before we start training the model, it’s crucial to prepare the data. The k-NN algorithm is very sensitive to the quality and format of the input data, so it’s essential to ensure that the data is correctly prepared for analysis. I will briefly mention the most important data processing steps, which you can use as a simple checklist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Missing Data
&lt;/h3&gt;

&lt;p&gt;If the dataset has missing values in some attributes, we must decide whether to fill in these values, remove them, or replace them. In most cases, we can replace missing values with the mean (imputation) or remove them if the number of missing values is negligible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Categorizing Data
&lt;/h3&gt;

&lt;p&gt;If we have categorical data (e.g., the category "experience level": beginner, intermediate, expert), we need to encode this data into numerical values. For k-NN, numerical values are required, so we could convert these categories into numbers (e.g., 1 for beginner, 2 for intermediate, and 3 for expert).&lt;/p&gt;

&lt;h3&gt;
  
  
  Outlier Detection and Removal
&lt;/h3&gt;

&lt;p&gt;It’s important to check if any data contains extreme values that could skew the results. These outliers should either be corrected or removed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Normalization/Scaling
&lt;/h3&gt;

&lt;p&gt;k-NN works on the principle of calculating distances between points in the data space. If we have attributes with different ranges of values (e.g., experience from 1 to 10 and coffee from 1 to 100), some attributes may dominate the distance calculation. Therefore, it’s important to normalize or scale these values so that each attribute has an equal impact on the computation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the practical example, I have already prepared a clean dataset.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  k-NN Algorithm Implementation
&lt;/h2&gt;

&lt;p&gt;The k-Nearest Neighbors (k-NN) algorithm is simple yet very powerful for classification. Its basic idea is that an unlabeled point is classified based on the classes of its nearest neighbors in the data space.&lt;/p&gt;

&lt;p&gt;The algorithm works as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;For each point in the test set, calculate the distance to all points in the training set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the "k" nearest neighbors (using Euclidean distance or other metrics).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Predict the class (e.g., successful/unsuccessful project) based on the majority class of the k nearest neighbors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We can use various distance metrics for calculating the distance, such as Euclidean distance, Manhattan distance, or Minkowski distance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this article, I’ve chosen the 3 most commonly used distance measurement methods to compare and select the most appropriate one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;

&lt;p&gt;Our task is to predict the success of a programming project based on three attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Experience&lt;/strong&gt; (the programmer’s experience).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tech Difficulty&lt;/strong&gt; (the technological difficulty).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Coffee&lt;/strong&gt; (the number of cups of coffee the programmer drinks while working).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtb59fszqevpqpqfumqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtb59fszqevpqpqfumqi.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each project in the test set, we will decide whether the project will be successful or unsuccessful based on how its attributes match with the attributes of projects in the training set. To do this, we’ll use the k-NN algorithm, which employs Euclidean distance to compute the similarity between projects.&lt;/p&gt;

&lt;p&gt;I have divided the project into several files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;data.csv&lt;/code&gt;: Our dataset, which contains a list of projects with attributes (experience, tech difficulty, coffee, and success).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;data.rs&lt;/code&gt;: Responsible for loading data from the CSV file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;distance.rs&lt;/code&gt;: Contains methods for distance calculation (Euclidean, Manhattan, and Minkowski distance).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;metric.rs&lt;/code&gt;: Contains metric methods (accuracy, precision, recall, and F1-score).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;knn&lt;/code&gt;: Handles the k-NN computation and prediction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;main.rs&lt;/code&gt;: Ties everything together and prints the results.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;In this article, I focus only on the most important functions (from &lt;code&gt;main.rs&lt;/code&gt;). The full project code can be found on GitHub: &lt;a href="https://github.com/mortylen/ml-knn-metrics-rs" rel="noopener noreferrer"&gt;https://github.com/mortylen/ml-knn-metrics-rs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;main.rs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;data&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;load_projects_from_csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;euclidean_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manhattan_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minkowski_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Weights&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;crate&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f1_score&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Load training data from CSV&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_projects_from_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data/data.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Split data into training and test sets - for simplicity, take the last 10 as test&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="nf"&gt;.split_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;train_vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_data&lt;/span&gt;&lt;span class="nf"&gt;.to_vec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Set "k" parameter&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Set weights for features&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Weights&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;experience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tech_difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;euclid_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;euclidean_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;manhattan_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manhattan_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;minkowski_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;minkowski_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Helper to predict labels for a test set&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;predict_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="py"&gt;.success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;predicted_labels_euclid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;predict_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;euclid_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;predicted_labels_manhattan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;predict_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manhattan_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;predicted_labels_minkowski&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;predict_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minkowski_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;print_all_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels_euclid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels_manhattan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels_minkowski&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Prediction for new input&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Project&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;experience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tech_difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// success is a placeholder&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Prediction for new project: experience={}, tech_difficulty={}, coffee={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="py"&gt;.experience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="py"&gt;.tech_difficulty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="py"&gt;.coffee&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Euclidean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;euclid_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Manhattan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manhattan_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Minkowski (p=3)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minkowski_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f1_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accuracy: {:.3}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Precision: {:.3}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Recall: {:.3}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"F1-Score: {:.3}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print_all_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;euclid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;manhattan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;minkowski&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-- Euclidean distance metrics --"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;euclid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;-- Manhattan distance metrics --"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manhattan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;-- Minkowski distance (p=3) metrics --"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;print_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minkowski&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pred&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"→ {} prediction: {} ({})"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pred&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"successful"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"unsuccessful"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Brief Explanation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, the dataset (&lt;code&gt;data.csv&lt;/code&gt;) is loaded and split into training and test sets:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Load training data from CSV&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_projects_from_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data/data.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Split data into training and test sets - for simplicity, take the last 10 as test&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="nf"&gt;.split_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;train_vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;train_data&lt;/span&gt;&lt;span class="nf"&gt;.to_vec&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setting the "k" parameter and the weights for the features:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set "k" parameter&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Set weights for features&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Weights&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;experience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tech_difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Defining the distance functions (Euclidean, Manhattan, and Minkowski distances):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;euclid_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;euclidean_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;manhattan_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manhattan_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;minkowski_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;minkowski_distance_weighted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Predicting the success of the projects in the test set using "k-NN":&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;predict_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist_fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DistanceFn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Evaluating performance (accuracy, precision, recall, and F1-score) and printing the results:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nf"&gt;print_all_metrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels_euclid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels_manhattan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels_minkowski&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, predicting the outcome of a new project (experience: 4, tech_difficulty: 2, coffee: 5):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Project&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;experience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tech_difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;coffee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="c1"&gt;// success is a placeholder&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Prediction for new project: experience={}, tech_difficulty={}, coffee={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="py"&gt;.experience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="py"&gt;.tech_difficulty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="py"&gt;.coffee&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Euclidean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;euclid_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Manhattan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manhattan_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nf"&gt;print_single_prediction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Minkowski (p=3)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;knn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;train_vec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;new_project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minkowski_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Further experimentation is needed with different settings of the "k" parameter, adjusting the weights (for example, we could give more weight to the number of cups of coffee ☕, which certainly has a significant impact on project success 😂), adding more data to the dataset, and trying different splits of training and test data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;After running the program, we will get a prediction for the success of the project based on its attributes (experience, technological difficulty, coffee). The program calculates the distances between the test project and all projects in the training set, selects the 3 nearest neighbors, and decides whether the project will be successful or unsuccessful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;--&lt;/span&gt; Euclidean distance metrics &lt;span class="nt"&gt;--&lt;/span&gt;
Accuracy: 0.800
Precision: 0.800
Recall: 1.000
F1-Score: 0.889

&lt;span class="nt"&gt;--&lt;/span&gt; Manhattan distance metrics &lt;span class="nt"&gt;--&lt;/span&gt;
Accuracy: 0.800
Precision: 0.800
Recall: 1.000
F1-Score: 0.889

&lt;span class="nt"&gt;--&lt;/span&gt; Minkowski distance &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt; metrics &lt;span class="nt"&gt;--&lt;/span&gt;
Accuracy: 0.800
Precision: 0.800
Recall: 1.000
F1-Score: 0.889

Prediction &lt;span class="k"&gt;for &lt;/span&gt;new project: &lt;span class="nv"&gt;experience&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4, &lt;span class="nv"&gt;tech_difficulty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2, &lt;span class="nv"&gt;coffee&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
→ Euclidean prediction: 1 &lt;span class="o"&gt;(&lt;/span&gt;successful&lt;span class="o"&gt;)&lt;/span&gt;
→ Manhattan prediction: 1 &lt;span class="o"&gt;(&lt;/span&gt;successful&lt;span class="o"&gt;)&lt;/span&gt;
→ Minkowski &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt; prediction: 1 &lt;span class="o"&gt;(&lt;/span&gt;successful&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Model Performance Evaluation
&lt;/h2&gt;

&lt;p&gt;Every machine learning model, including our k-NN algorithm, can be evaluated based on how well it predicts outcomes on new data. Without evaluation, we wouldn’t be able to determine whether our model is actually good or if it needs improvement. k-NN is very sensitive to data quality, the choice of the "k" parameter, and the correct evaluation method. To this end, we use various evaluation metrics to help us understand how the model behaves in different situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accuracy
&lt;/h3&gt;

&lt;p&gt;Accuracy is the simplest metric that tells us the percentage of correct predictions made by the model. It is the most commonly used metric, especially when we have balanced data, meaning an equal number of cases for both classes.&lt;/p&gt;

&lt;p&gt;$$\text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN}$$&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TP&lt;/strong&gt;: True Positives — correctly predicted positive instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TN&lt;/strong&gt;: True Negatives — correctly predicted negative instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;FP&lt;/strong&gt;: False Positives — negative instances incorrectly predicted as positive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;FN&lt;/strong&gt;: False Negatives — positive instances incorrectly predicted as negative.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Simple to understand and interpret.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suitable for balanced datasets where the classes are equally represented.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can be misleading when we have imbalanced data (e.g., when there are more negative cases than positive ones). If the number of negative cases is much higher than the positive ones, accuracy may show a high value even when the model never predicts the positive class. For example, if we have 95% negative cases, a model that always predicts the negative class might achieve 95% accuracy, even though it wouldn’t be useful.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;correct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;correct&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Precision
&lt;/h3&gt;

&lt;p&gt;Precision indicates how many of the positive predictions were actually correct. It is important when we want to minimize False Positives (incorrect positive predictions), such as in disease diagnosis, where we don’t want the model to incorrectly label a healthy person as sick.&lt;/p&gt;

&lt;p&gt;$$\text{Precision} = \frac{TP}{TP + FP}$$&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Useful for minimizing false positives. It can be very important to prevent the model from labeling a positive case (e.g., spam email or disease) as negative.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suitable in cases where there are high costs associated with false positives, such as disease testing, where an incorrect diagnosis could lead to unnecessary treatment.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does not account for false negatives. In situations where we also care about capturing all positive cases (e.g., disease detection), precision may not be enough.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recall
&lt;/h3&gt;

&lt;p&gt;Recall, also known as Sensitivity or True Positive Rate, indicates how many of the actual positive cases were correctly identified. It is important when we want to minimize False Negatives (incorrectly missed positive predictions), such as in rare disease detection, where it is crucial not to miss any positive cases.&lt;/p&gt;

&lt;p&gt;$$\text{Recall} = \frac{TP}{TP + FN}$$&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Minimizes false negatives. For certain applications, it is important not to miss any positive case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suitable for cases where detecting as many positive cases as possible is important, even if it leads to a higher number of false positives.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does not account for false positives, which can lead to the model predicting many incorrect positive cases.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fn_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.filter&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fn_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fn_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  F1-Score
&lt;/h3&gt;

&lt;p&gt;The F1-Score is the harmonic mean between Precision and Recall. This metric is very useful when we do not want to favor one method over the other and need a balance between minimizing False Positives and False Negatives.&lt;/p&gt;

&lt;p&gt;$$\text{F1} = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}$$&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Balances Precision and Recall. The F1-Score is useful when we do not know whether it is more important to minimize False Positives or False Negatives.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suitable for imbalanced data, where one type of error (False Positives or False Negatives) may have a greater impact.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not always intuitive, as it’s not as straightforward to interpret as Accuracy or Precision.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;f1_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which Metric is the Right One?
&lt;/h2&gt;

&lt;p&gt;Choosing the right evaluation metric is crucial for effectively assessing model performance, especially in classification tasks. Accuracy, Precision, Recall, and F1-Score are just some of the many metrics used to evaluate a model. Each of these metrics has its advantages and disadvantages, and the right choice depends on the nature of the problem and the data available.&lt;/p&gt;

&lt;p&gt;When selecting metrics, it’s important to consider what matters most to you: whether to minimize False Positives or False Negatives, or simply maximize accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Metric Comparison:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;true_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;predicted_labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;prec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;precision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f1_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;true_labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;predicted_labels&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accuracy: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Precision: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Recall: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"F1-Score: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evaluating the model provides key insights into how it behaves in practice, helping us decide if it’s accurate enough for deployment in the real world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accuracy:&lt;/strong&gt; Let’s imagine our model predicts whether a product is "cheap" or "expensive," and we have 100 products, with 50 labeled as "cheap" and 50 as "expensive." If our model correctly predicted 80 of these 100 products, its Accuracy would be:&lt;/p&gt;

&lt;p&gt;$$Accuracy = \frac{80}{100} = 0.8 = 80\%$$&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Precision:&lt;/strong&gt; If we have a model predicting spam emails and we don’t want the model to wrongly label legitimate emails as spam, it’s crucial that the model has high Precision. If the model labeled 20 emails as spam, of which 18 were indeed spam, the Precision would be:&lt;/p&gt;

&lt;p&gt;$$Precision = \frac{18}{20} = 0.9 = 90\%$$&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recall&lt;/strong&gt;: Let’s imagine a model predicting whether a patient has a disease (e.g., cancer). If it’s crucial to capture all truly sick patients, even if some healthy patients are mistakenly labeled as sick (False Positives), we would prioritize Recall. If the model caught 15 out of 20 truly sick patients, the Recall would be:&lt;/p&gt;

&lt;p&gt;$$Recall = \frac{15}{20} = 0.75 = 75\%$$&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F1-Score:&lt;/strong&gt; In the case where a model predicts whether a product is "cheap" or "expensive" and we care about balancing the accuracy and completeness of predictions (i.e., balancing Precision and Recall), we use the F1-Score. If the model achieved Precision = 0.8 and Recall = 0.6, the F1-Score would be:&lt;/p&gt;

&lt;p&gt;$$F1 = 2 \times \frac{0.8 \times 0.6}{0.8 + 0.6} = 0.6857 = 68.57\%$$&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the case of our example with programmers, technical difficulty of projects, and coffee consumption, F1-Score would be the most suitable metric because:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The technical difficulty of projects and coffee consumption habits of programmers might be imbalanced (e.g., most projects could be "low difficulty," but we still need to capture the "high difficulty" ones).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coffee is just one of the factors, and it’s not entirely certain that a high-difficulty project automatically means the programmer will drink coffee.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We want to achieve a balance between precision (i.e., when the model says the programmer drinks coffee on a difficult project, it’s correct) and recall (i.e., we capture all those who actually drink coffee, even if the model might make some wrong predictions).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Model Tuning and Optimization
&lt;/h2&gt;

&lt;p&gt;After evaluating our model and selecting the appropriate evaluation metric, the time comes to optimize it. No matter how good the model is initially, it can always be improved. We will focus on various techniques that can be used to tune a k-NN (k-Nearest Neighbors) model, particularly the selection of the optimal number of "k" (k-nearest neighbors), choosing a distance metric, and scaling the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selecting the Optimal Number of k (k-Nearest Neighbors)
&lt;/h3&gt;

&lt;p&gt;One of the most important hyperparameters in k-NN algorithms is the number "k", which determines how many nearest neighbors are used to classify a new point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Small k (e.g., k = 1):&lt;/strong&gt; The model becomes overly sensitive to individual outliers in the data and may suffer from overfitting (it fits too much to the training data).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large k (e.g., k = 100):&lt;/strong&gt; The model becomes smoother, as it makes decisions based on a larger number of neighbors. However, if "k" is too large, the model may overlook details and suffer from underfitting.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The optimal "k" can be found in several ways, such as trying different values of "k" and observing how Accuracy, Precision, Recall, and F1-Score change. Alternatively, cross-validation can be used, where the data is split into multiple subsets (folds), and the model is trained with different "k" values on each fold to find the optimal value that minimizes error.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If we use k = 3, we may notice that the model is more sensitive to details, resulting in higher Precision but lower Recall. If we set k = 7, the model will be less sensitive to fluctuations, which may improve Recall but reduce Precision.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Selecting the Distance Metric
&lt;/h3&gt;

&lt;p&gt;The k-NN algorithm also depends on the selection of the metric used to measure the distance between points. While Euclidean distance is the most common, there are other metrics that may be more appropriate for different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Euclidean Distance (L2 norm):&lt;/strong&gt; The most common metric, suitable for continuous data (such as our example with technical difficulty and coffee habits).&lt;/p&gt;

&lt;p&gt;$$d(x, y) = \sqrt{ \sum_{i=1}^{n} (x_i - y_i)^2 }$$&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;euclidean_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Vectors must have the same length"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
     &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.powi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
     &lt;span class="py"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="nf"&gt;.sqrt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;euclidean_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Euclidean distance: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Manhattan Distance (L1 norm):&lt;/strong&gt; This metric is used when the data is discrete or when the differences between values are "more even" and we don't want large fluctuations between neighbors to have a significant impact.&lt;/p&gt;

&lt;p&gt;$$d(x, y) = \sum_{i=1}^{n} |x_i - y_i|$$&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;manhattan_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Vectors must have the same length"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
     &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.abs&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
     &lt;span class="nf"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;manhattan_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Manhattan distance: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Minkowski Distance:&lt;/strong&gt; This generalizes both of the previous distances. It includes a "p" parameter, which determines the weight assigned to each dimension. For &lt;code&gt;p=2&lt;/code&gt;, we get Euclidean distance, and for &lt;code&gt;p=1&lt;/code&gt;, we get Manhattan distance.&lt;/p&gt;

&lt;p&gt;$$d(x, y) = \left( \sum_{i=1}^{n} |x_i - y_i|^p \right)^{\frac{1}{p}}$$&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;minkowski_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;f32&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Vectors must have the same length"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="nf"&gt;.zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.iter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
     &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;xi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.abs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.powf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
     &lt;span class="py"&gt;.sum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;f32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="nf"&gt;.powf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;distance_p1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;minkowski_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Manhattan distance&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;distance_p2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;minkowski_distance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Euclidean distance&lt;/span&gt;

    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Minkowski distance (p=1, Manhattan): {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance_p1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Minkowski distance (p=2, Euclidean): {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distance_p2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Classification using k-NN is an extremely powerful tool in machine learning, especially for simple problems where it is necessary to quickly and efficiently predict the class based on historical data. However, selecting the right metric for evaluating the model is crucial for success in practice. Accuracy, precision, recall, and F1-score are all useful in different applications, and the correct metric can significantly affect the model's performance in real-world conditions.&lt;/p&gt;

&lt;p&gt;k-NN is not perfect for all types of tasks. When focusing on improving the model's performance, we need to concentrate on optimizing parameters such as "k", selecting the appropriate distance metric, weighting neighbors, and normalizing the data. Thanks to these improvements, k-NN can be a powerful tool in a wide range of applications, which can be tailored to the needs of our users or specific tasks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;More about machine learning algorithms and metrics can be found in a separate project:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://mlcompassguide.dev/" rel="noopener noreferrer"&gt;https://mlcompassguide.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository for this article can be found on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/mortylen/ml-knn-metrics-rs" rel="noopener noreferrer"&gt;https://github.com/mortylen/ml-knn-metrics-rs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’ve made it this far, congratulations! I hope the article was useful and inspired you with new ideas or experiences. If you have any questions or comments, feel free to share them in the comments.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📷 Cover photo by &lt;a href="https://unsplash.com/photos/a-cup-of-coffee-next-to-a-computer-keyboard-YJv9MGGQks8" rel="noopener noreferrer"&gt;Amr Taha™&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>datascience</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>I’ve Started a New Machine Learning Project</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Sun, 24 Aug 2025 09:09:16 +0000</pubDate>
      <link>https://forem.com/mortylen/ive-started-a-new-machine-learning-project-29i5</link>
      <guid>https://forem.com/mortylen/ive-started-a-new-machine-learning-project-29i5</guid>
      <description>&lt;p&gt;Over the last few months, I’ve been working on a small project to organize my knowledge of machine learning algorithms, data processing techniques, and evaluation metrics.&lt;/p&gt;

&lt;p&gt;At first, it was just a personal learning exercise, meant to better understand the concepts and keep everything structured. But then I realized it could also be useful for others who are trying to navigate the ML landscape, so I decided to make it public.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s the goal?
&lt;/h2&gt;

&lt;p&gt;The purpose of ML Compass Guide is to make the world of machine learning easier to navigate.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A clear decision map that shows where each algorithm belongs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explanations of algorithms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Practical code examples.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A collection of metrics methods that help you evaluate models.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current state
&lt;/h2&gt;

&lt;p&gt;The project is still in its early stages (so expect a few rough edges 😅). Some sections are complete, others are just placeholders for now. But I’ll keep expanding it step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you can help
&lt;/h2&gt;

&lt;p&gt;I’d love to get feedback from the community!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is the structure clear?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What would make it more useful for you?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are there algorithms/metrics you’d like to see next?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 You can check it out here: &lt;a href="https://mlcompassguide.dev/" rel="noopener noreferrer"&gt;mlcompassguide.dev&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>programming</category>
      <category>machinelearning</category>
      <category>rust</category>
    </item>
    <item>
      <title>Change Data Capture in SQL Server</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Sat, 28 Jun 2025 19:19:56 +0000</pubDate>
      <link>https://forem.com/mortylen/change-data-capture-in-sql-server-87f</link>
      <guid>https://forem.com/mortylen/change-data-capture-in-sql-server-87f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Change Data Capture functionality is &lt;strong&gt;not available&lt;/strong&gt; in the SQL Server Express edition. It is supported only in higher editions such as Standard, Enterprise, or Developer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Main Components
&lt;/h2&gt;

&lt;p&gt;The main components of CDC in SQL Server include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transaction Log&lt;/strong&gt;: Serves as the source of information about changes made in database tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Capture Job&lt;/strong&gt;: A SQL Agent job that regularly reads the transaction log and extracts changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup Job&lt;/strong&gt;: A SQL Agent job that removes older records from CDC tables based on retention settings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CDC Capture Tables&lt;/strong&gt;: System tables where captured changes are stored, including operation type, original and new values.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz77vbcb9auwin80j5xz8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz77vbcb9auwin80j5xz8.jpg" alt="change data capture workflow" width="480" height="693"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change in monitored table&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The user performs an &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; operation on a monitored table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recording in transaction log&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
SQL Server records this change in the database’s transaction log.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CDC Capture Job reads transaction log&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
SQL Server Agent regularly (typically every few seconds) runs the &lt;strong&gt;CDC capture job&lt;/strong&gt;, which reads new change records for all monitored tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Writing changes to CDC capture tables&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The job extracts information about the changes (original and new values, operation type, timestamp) and stores them in system &lt;strong&gt;CDC tables&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup Job deletes old records&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Based on the configured retention period (e.g., 3 days), the &lt;strong&gt;CDC cleanup job&lt;/strong&gt; runs to remove old records from CDC tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User reads changes&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The user can query special &lt;strong&gt;views&lt;/strong&gt; to retrieve changes for a specified time period and process them further.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User modifies table (INSERT/UPDATE/DELETE)
       ↓
Transaction Log (records changes)
       ↓
CDC Capture Job (reads changes from log)
       ↓
CDC Capture Tables (e.g. cdc.dbo_Employees_CT)
       ↓
CDC Cleanup Job (deletes old records)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up and Testing CDC
&lt;/h2&gt;

&lt;p&gt;Now that we understand what the technology is about, let's proceed with a simple test.&lt;br&gt;&lt;br&gt;
We will create a test database and a table within it, where we will perform changes and verify their capture using CDC.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create Database and Table
&lt;/h3&gt;

&lt;p&gt;Let's create a test database, for example &lt;code&gt;TestCDC&lt;/code&gt;, and within it a table called &lt;code&gt;Employees&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create Database&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;TestCDC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="c1"&gt;-- Create Table&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;TestCDC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;Position&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Rating&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enable Change Data Capture
&lt;/h3&gt;

&lt;p&gt;Now that we have something to test on, let's enable Change Data Capture on our new database and table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enable CDC for Database&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;TestCDC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_enable_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="c1"&gt;-- Enable CDC for Table&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_enable_table&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;source_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'dbo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;source_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'Employees'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;role_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;supports_net_changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@source_schema&lt;/code&gt;: Name of the schema where the tracked table is located.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@source_name&lt;/code&gt;: Name of the table we want to track.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@role_name&lt;/code&gt;: Name of the database role that will have access to CDC data. If set to &lt;code&gt;NULL&lt;/code&gt;, anyone with access to the database will be able to access the CDC data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@supports_net_changes&lt;/code&gt;: If set to &lt;code&gt;1&lt;/code&gt;, enables the "net change" mode – an aggregated view of the changes (only the latest version of each row). If set to &lt;code&gt;0&lt;/code&gt;, all changes are recorded in detail.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test Change Data Capture
&lt;/h2&gt;

&lt;p&gt;We have everything set up for our testing. CDC is enabled for both the database and the tracked table.&lt;br&gt;&lt;br&gt;
Let's test it by inserting, updating, and deleting records in the table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;TestCDC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="c1"&gt;-- Insert data&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Rating&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'John Novak'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Analytic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Peter Burn'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Programmer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="c1"&gt;-- Update data&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;Rating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;Position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Gardener'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="c1"&gt;-- Delete data&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Employees&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our test database &lt;code&gt;TestCDC&lt;/code&gt;, we should now see several new system tables that contain the captured changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cdc.captured_columns&lt;/strong&gt;: A list of columns that are being tracked by CDC.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cdc.change_tables&lt;/strong&gt;: Metadata about all CDC instances in the database, i.e., which tables are being tracked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cdc.dbo_Employees_CT&lt;/strong&gt;: Change data (&lt;code&gt;INSERT&lt;/code&gt;/&lt;code&gt;UPDATE&lt;/code&gt;/&lt;code&gt;DELETE&lt;/code&gt;) for the specific &lt;code&gt;Employees&lt;/code&gt; table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cdc.ddl_history&lt;/strong&gt;: History of DDL changes on tracked tables (e.g., &lt;code&gt;ALTER TABLE&lt;/code&gt;), records structural modifications to the tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cdc.index_columns&lt;/strong&gt;: Information about indexes on tracked tables, mainly primary keys used for identifying changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;cdc.lsn_time_mapping&lt;/strong&gt;: Mapping of LSN (Log Sequence Number) to the actual change time (&lt;code&gt;datetime&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;dbo.systranschemas&lt;/strong&gt;: Internal helper table used to identify schemas for CDC.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's try a few simple queries and look at their results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing Captured Changes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Change data&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbo_Employees_CT&lt;/span&gt;

&lt;span class="c1"&gt;-- Added timestamp&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fn_cdc_map_lsn_to_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;start_lsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;CaptureTimeStamp&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbo_Employees_CT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- A slightly cleaner view&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;start_lsn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fn_cdc_map_lsn_to_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;start_lsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;CaptureTimeStamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="k"&gt;operation&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'DELETE'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'INSERT'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE (old)'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'UPDATE (new)'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;OperationType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Rating&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbo_Employees_CT&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;__&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;start_lsn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of the &lt;code&gt;__$operation&lt;/code&gt; field indicates the type of change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;1&lt;/code&gt; = delete (&lt;code&gt;DELETE&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;2&lt;/code&gt; = insert (&lt;code&gt;INSERT&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;3&lt;/code&gt; = old row (previous state during an &lt;code&gt;UPDATE&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;4&lt;/code&gt; = new row (new state during an &lt;code&gt;UPDATE&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmc6cb66p4lkacz79ob2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzmc6cb66p4lkacz79ob2.png" alt="table of results" width="714" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Disabling Change Data Capture
&lt;/h3&gt;

&lt;p&gt;If change tracking is no longer needed, it's a good idea to disable CDC.&lt;br&gt;&lt;br&gt;
This saves database performance and disk space.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;TestCDC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;

&lt;span class="c1"&gt;-- Stops tracking changes for a single table&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_disable_table&lt;/span&gt; 
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;source_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'dbo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;source_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'Employees'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;capture_instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'dbo_Employees'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="c1"&gt;-- Disables CDC for the entire database&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_disable_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Bit About Configuration
&lt;/h3&gt;

&lt;p&gt;We have CDC set up and change tracking is working quite well. But what about maintenance and performance?&lt;br&gt;&lt;br&gt;
Change Data Capture automatically deletes old records every few days.&lt;br&gt;&lt;br&gt;
It relies on two main SQL Server Agent Jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Capture Job&lt;/strong&gt;: Reads the transaction log and copies changes into the CDC tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup Job&lt;/strong&gt;: Regularly deletes old records from the CDC tables based on the retention settings.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These jobs can be configured according to our preferences.&lt;/p&gt;
&lt;h4&gt;
  
  
  Capture Job – Settings:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;maxtrans&lt;/code&gt;: Maximum number of transactions processed in a single batch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;maxscans&lt;/code&gt;: Maximum number of log reads before a short pause.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;continuous&lt;/code&gt;: &lt;code&gt;1&lt;/code&gt; = job runs continuously, &lt;code&gt;0&lt;/code&gt; = runs once (useful for testing).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pollinginterval&lt;/code&gt;: Number of seconds between log scans (applies only if &lt;code&gt;continuous = 1&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example: sets the log polling interval to every 5 seconds.&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_change_job&lt;/span&gt; 
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'capture'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;pollinginterval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Cleanup Job – Settings:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;retention&lt;/code&gt;: Number of minutes after which records in the CDC &lt;code&gt;.CT&lt;/code&gt; tables are removed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;threshold&lt;/code&gt;: Number of records deleted in a single batch (performance control).&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Example: sets cleanup of old data to run on a daily cycle.&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_change_job&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'cleanup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;retention&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;--one day in minute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Restarting CDC Jobs After Configuration Changes
&lt;/h4&gt;

&lt;p&gt;When changing job settings, it is necessary to restart these services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Restart Capture Job.&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_stop_job&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'capture'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_start_job&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'capture'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Restart Cleanup Job.&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_stop_job&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'cleanup'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_start_job&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;job_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'cleanup'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Monitoring the Size of CDC Tables
&lt;/h4&gt;

&lt;p&gt;In a production environment, it is advisable to monitor the behavior of different configurations and their impact on database size and resource usage. For example, monitoring the size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_page_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;SizeMB&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_db_partition_stats&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;schema_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SCHEMA_ID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cdc'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;SizeMB&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;em&gt;For Larger Tables and Frequent Changes, Try:&lt;/em&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;For larger tables and frequent changes, try reducing the&lt;/em&gt; &lt;code&gt;@retention&lt;/code&gt; &lt;em&gt;from the default 3 days to less, for example to 1 day (1440 minutes).&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Depending on CPU performance, try increasing the&lt;/em&gt; &lt;code&gt;@threshold&lt;/code&gt; &lt;em&gt;for more efficient cleanup.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;For frequent changes, you can reduce the&lt;/em&gt; &lt;code&gt;@pollinginterval&lt;/code&gt; &lt;em&gt;so the capture job reacts faster and processes changes more quickly.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The status of individual CDC jobs can also be monitored using &lt;code&gt;sys.sp_cdc_help_jobs&lt;/code&gt;. This is a system stored procedure in SQL Server that provides information about the Change Data Capture agent jobs in the database.&lt;br&gt;&lt;br&gt;
It helps to find out which CDC agent jobs are configured, whether they are running correctly, their status, and more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- List and status of CDC Jobs.&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp_cdc_help_jobs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  CDC Job Status Output by SQL Server Version
&lt;/h4&gt;

&lt;p&gt;The output may vary depending on the SQL Server version. Versions 2022 and newer report the job status directly. Older versions (2012 - 2019) only show the job settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQL Server Version 2022 and Newer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;job_type&lt;/code&gt;: &lt;code&gt;capture&lt;/code&gt; or &lt;code&gt;cleanup&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;job_id&lt;/code&gt;: GUID identifier of the SQL Agent Job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;enabled&lt;/code&gt;: &lt;code&gt;1&lt;/code&gt; = job is enabled, &lt;code&gt;0&lt;/code&gt; = job is disabled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;status&lt;/code&gt;: Job status (numeric value, e.g., &lt;code&gt;1&lt;/code&gt; = running).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;last_run_date&lt;/code&gt;: Last run date of the job (YYYYMMDD).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;last_run_time&lt;/code&gt;: Last run time of the job (HHMMSS).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;last_run_outcome&lt;/code&gt;: Result of the last run (&lt;code&gt;0&lt;/code&gt; = failure, &lt;code&gt;1&lt;/code&gt; = success).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;message&lt;/code&gt;: Text message (if any).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SQL Server Version 2012 – 2019:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;job_id&lt;/code&gt;: Unique job identifier (GUID).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;job_type&lt;/code&gt;: Job type: &lt;code&gt;capture&lt;/code&gt; or &lt;code&gt;cleanup&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;job_name&lt;/code&gt;: Name of the job in SQL Server Agent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;maxtrans&lt;/code&gt;: (capture only) Maximum number of transactions processed in one batch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;maxscans&lt;/code&gt;: (capture only) Maximum number of log scans per cycle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;continuous&lt;/code&gt;: &lt;code&gt;1&lt;/code&gt; = job runs continuously (CDC capture job), &lt;code&gt;0&lt;/code&gt; = no.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pollinginterval&lt;/code&gt;: Time (in seconds) between log scans if &lt;code&gt;continuous = 1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;retention&lt;/code&gt;: (cleanup only) Number of minutes records are retained in CT tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;threshold&lt;/code&gt;: (cleanup only) Number of records cleaned in one batch.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to check the status on an older server, you can use the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;JobName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ja&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_execution_date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;LastStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ja&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stop_execution_date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;LastStop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ja&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_requested_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ja&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_requested_source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- 1 = Success, 0 = Failed&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;msdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sysjobs&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;msdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sysjobactivity&lt;/span&gt; &lt;span class="n"&gt;ja&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ja&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;msdb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sysjobhistory&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'cdc.%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Note
&lt;/h2&gt;

&lt;p&gt;Before using CDC, it is recommended to set the &lt;strong&gt;recovery model&lt;/strong&gt; to &lt;strong&gt;FULL&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
The recovery model has a crucial impact on how CDC works because CDC does not read directly from the tracked table, but from the transaction log.&lt;br&gt;&lt;br&gt;
It is important how long the records are retained in the log — if the recovery model is set to &lt;strong&gt;SIMPLE&lt;/strong&gt;, the log records might be deleted before CDC can process them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checking the Recovery Model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recovery_model_desc&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;databases&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YUOR-DATABASE-NAME'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Changing the Recovery Model:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;YOUR&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;RECOVERY&lt;/span&gt; &lt;span class="k"&gt;FULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Things to Watch Out For
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unbacked log&lt;/strong&gt;: The log grows indefinitely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CDC job not running&lt;/strong&gt;: No new records, risk of data loss.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Large INSERT/UPDATE&lt;/strong&gt;: Can cause temporary performance degradation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Too long retention&lt;/strong&gt;: Unnecessarily large &lt;code&gt;.CT&lt;/code&gt; tables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CDC enabled on a table without a PK&lt;/strong&gt;: Difficult to track changed rows.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Change Data Capture is a very powerful tool for recording changes in SQL Server, minimizing performance impact, eliminating the need for custom triggers, and ideal for data warehousing, replication, and monitoring. A properly configured CDC system allows you to track and analyze all changes in data without complex logic or manual intervention.&lt;/p&gt;




&lt;p&gt;If you found this useful, consider supporting me:&lt;/p&gt;

&lt;p&gt;☕ &lt;a href="https://buymeacoffee.com/mortylen" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;👉 &lt;em&gt;My github profile&lt;/em&gt; &lt;a href="https://github.com/mortylen" rel="noopener noreferrer"&gt;&lt;em&gt;GitHub&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;em&gt;My blog page&lt;/em&gt; &lt;a href="https://mortylen.hashnode.dev/" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>sql</category>
      <category>sqlserver</category>
      <category>database</category>
    </item>
    <item>
      <title>🦀 Back to Rust!</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Sun, 15 Jun 2025 17:26:59 +0000</pubDate>
      <link>https://forem.com/mortylen/back-to-rust-4ef1</link>
      <guid>https://forem.com/mortylen/back-to-rust-4ef1</guid>
      <description>&lt;p&gt;A while ago, I built these two small projects in Rust:&lt;br&gt;
🔹 &lt;a href="https://dev.to/mortylen/joule-heat-calculator-5g73"&gt;Joule heat calculator&lt;/a&gt;&lt;br&gt;
🔹 &lt;a href="https://dev.to/mortylen/openai-language-lector-4fe7"&gt;OpenAI Language Lector&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I’m diving into a Rust training course, and I’m curious (and slightly terrified) to discover how many things I got wrong back then 😅&lt;/p&gt;

&lt;p&gt;Looking forward to learning what to fix… or maybe rewrite completely! Stay tuned for updates.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>programming</category>
      <category>learning</category>
    </item>
    <item>
      <title>Gitea Self-Hosted Workflow Action For CI</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Thu, 27 Jun 2024 17:04:40 +0000</pubDate>
      <link>https://forem.com/mortylen/gitea-self-hosted-workflow-action-for-ci-1bhf</link>
      <guid>https://forem.com/mortylen/gitea-self-hosted-workflow-action-for-ci-1bhf</guid>
      <description>&lt;p&gt;In this short tutorial I would like to describe the installation and setup of CI/CD Actions for a self-hosted Gitea server running on an Ubuntu. I will describe a script for testing and compiling code written in C# in a Visual Studio environment. I decided to separate the Actions server to a separate Ubuntu server instance for easier administration and to keep running processes from overwhelming the standalone git server. Anyway, it is possible to run Actions on the same server as Gitea, or to run Runners in Dockers. I described the installation and setup of a self-hosted git server in the previous article &lt;a href="https://dev.to/mortylen/easy-self-hosted-git-installation-on-ubuntu-server-2o4c"&gt;Gitea Self-Hosted Action Ubuntu Server&lt;/a&gt;. All the steps described below assume that you already have Gitea (Git-Server) installed.&lt;/p&gt;

&lt;p&gt;Gitea Actions consists of several components. For our purpose, it is enough to know that we need ActRunner to run Actions. Like other CI Runners, ActRunner is designed to run independently on another server. It can be run using Docker or directly on the host. In this guide I will focus on running with the Docker engine. More information can be found on the official &lt;a href="https://docs.gitea.com/next/usage/actions/overview/" rel="noopener noreferrer"&gt;Gitea website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;The first component we will need is Docker. Using Docker we will later start ActRunner. For more information about installing Docker, see the &lt;a href="https://docs.docker.com/engine/install/ubuntu/" rel="noopener noreferrer"&gt;official guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's do this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Update local packaged:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo apt update&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allow APT to access repositories via the HTTPS orotocol:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo apt install apt-transport-https ca-certificates curl software-properties-common&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the Docket GNU Privacy Guard key to the APT keyring:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the Docker repository to the APT package manager:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prepare an installation from a Docker repository:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ apt-cache policy docker-ce&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install Docker:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo apt install docker-ce&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the status of the Docket service:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo systemctl status docker&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobhjs2jxh7ahim747gp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobhjs2jxh7ahim747gp7.png" alt="Docker Status" width="661" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Enable Docker to start automatically on system boot:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo systemctl enable docker&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding an account to a Docker group:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo usermod -aG docker ${USER}&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set permissions to run the service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo chmod +x /var/run/docker.sock&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Act Runner
&lt;/h2&gt;

&lt;p&gt;Once we have Docker up and running, we can start installing ActRunner. In order for ActRunner to run on a separate server, or in a separate container, and connect to a correct Gitea instance, we need to register it with a token.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the current version of ActRunner using &lt;code&gt;wget&lt;/code&gt;. Replace the URL with the desired version. We recommend opting for the latest version. Here's an example for 64-bit Linux, version 0.2.10. For the full list, visit &lt;a href="https://dl.gitea.com/act_runner/" rel="noopener noreferrer"&gt;https://dl.gitea.com/act_runner/&lt;/a&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;wget &lt;span class="nt"&gt;-O&lt;/span&gt; act_runner https://dl.gitea.com/act_runner/0.2.10/act_runner-0.2.10-linux-amd64
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x act_runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Check the version of ActRunner:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ ./act_runner --version&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr2s23kxon6i4pyl13mn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr2s23kxon6i4pyl13mn.png" alt="ActRunner version" width="661" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ActRunner registration is important for the Runner to know for which Gitea instance to run the jobs. For this you need to generate a token. Gitea provides three levels of tokens:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instance level:&lt;/strong&gt; The admin settings page, like &lt;em&gt;&amp;lt;your_gitea.com&amp;gt;/admin/actions/runners&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Organization level:&lt;/strong&gt; The organization settings page, like &lt;em&gt;&amp;lt;your_gitea.com&amp;gt;/org/settings/actions/runners&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository level:&lt;/strong&gt; The repository settings page, like &lt;em&gt;&amp;lt;your_gitea.com&amp;gt;/settings/actions/runners&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find your token on Gitea &lt;em&gt;&amp;lt;your_gitea.com&amp;gt;/admin/actions/runners&lt;/em&gt; under &lt;strong&gt;&lt;em&gt;Create new runner&lt;/em&gt;&lt;/strong&gt;. Or separately for each repository &lt;em&gt;&amp;lt;your_gitea.com&amp;gt;/settings/actions/runners&lt;/em&gt; under &lt;strong&gt;&lt;em&gt;Create new runner&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj8ylfbqyy0lhyw802gy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpj8ylfbqyy0lhyw802gy.png" alt="Gitea Create new Ranner" width="762" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of &lt;code&gt;&amp;lt;INSTANCE&amp;gt;&lt;/code&gt; enter your own URL and instead of &lt;code&gt;&amp;lt;TOKEN&amp;gt;&lt;/code&gt; enter your own token:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ ./act_runner register --no-interactive --instance &amp;lt;INSTANCE&amp;gt; --token &amp;lt;TOKEN&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For Example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ ./act_runner register --no-interactive --instance http://192.168.52.130:3000 --token MyAyfw5v4i8hwVGZR9NXjW0ikIHOXXXXXXXXXXXX&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyw0wofmv89evs53snqfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyw0wofmv89evs53snqfa.png" alt="Gitea Runner Registred" width="634" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;After registration, all you have to do is launch ActRunner using Daemon:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo ./act_runner daemon&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46gpdd2o0q4h0jda22a0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46gpdd2o0q4h0jda22a0.png" alt="Start Runner" width="642" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should now see the service tunning in the Gitea web environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljp41yi8vmb36qcn793h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fljp41yi8vmb36qcn793h.png" alt="Gitea web ranner list" width="644" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want the service to start automatically after rebootong the server, write a simple bash script and add it to &lt;strong&gt;&lt;em&gt;Crontab&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a file and insert the following script into it. Change the &lt;code&gt;&amp;lt;USER&amp;gt;&lt;/code&gt; to your administrator account or choose any other location to store the file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ nano /home/&amp;lt;USER&amp;gt;/start_act_runner.sh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Insert the script into the created file and edit the path to &lt;code&gt;act_runner&lt;/code&gt; if it is different:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;60        &lt;span class="c"&gt;# waiting for all services to be started&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/&amp;lt;USER&amp;gt;
./act_runner daemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Enable the execution rule of our new script:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo chmod +x /home/&amp;lt;USER&amp;gt;/start_act_runner.sh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open and edit &lt;em&gt;Crontab&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ crontab -e&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding an instruction to the &lt;strong&gt;&lt;em&gt;Crontab&lt;/em&gt;&lt;/strong&gt;. Ensures that the script is run after the server restarts. Change &lt;code&gt;&amp;lt;USER&amp;gt;&lt;/code&gt; to the administrator account or location where you saved the script:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@reboot /home/&amp;lt;USER&amp;gt;/start_act_runner.sh&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndki1i5abpfsy5b62u0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fndki1i5abpfsy5b62u0r.png" alt="Crontab Add Rule" width="661" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Write Workflow Action
&lt;/h2&gt;

&lt;p&gt;Now that everything is set up and the Runner is running, we can create a script to automate the building and testing of source code written in the Visual Studio environment.&lt;/p&gt;

&lt;p&gt;We'll start by creating a simple console application for testing. In this application, we'll write a basic class, such as &lt;code&gt;MyMath&lt;/code&gt;, which will contain a function to add two numbers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyMath&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num1&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;num2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will add a new &lt;strong&gt;&lt;em&gt;NUnit Test Project&lt;/em&gt;&lt;/strong&gt; to our solution. Left-click on your &lt;strong&gt;&lt;em&gt;Solution&lt;/em&gt;&lt;/strong&gt; and select &lt;strong&gt;&lt;em&gt;Add -&amp;gt; New Project&lt;/em&gt;&lt;/strong&gt;. Visual Studio will automatically install all the necessary NUnit packages. In the newly created NUnit test project, we will add a dependency on our console application to access the &lt;code&gt;MyMath&lt;/code&gt; class. Now, we can write a simple test for our mathematical function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6hllw8qu5cfyzq9o84h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm6hllw8qu5cfyzq9o84h.png" alt="VS Add NUnit project" width="800" height="285"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;TestProject1&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Tests&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;MyMath&lt;/span&gt; &lt;span class="n"&gt;_myMath&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SetUp&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_myMath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MyMath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;100010.1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;(-&lt;/span&gt;&lt;span class="m"&gt;100000.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;10.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;100010.1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Verifies that the MyMath.Add() function works correctly with real numbers."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MyMath_Add_RealNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;number1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;number2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Act&lt;/span&gt;
            &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_myMath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Assert&lt;/span&gt;
            &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;$"Not Correct: (&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) + (&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to add dependency to your test project. As shown, three test cases are performed. The first test case checks positive numbers, the second tests negative numbers, and the last one tests zero. If the function calculates correctly, the test should pass.&lt;/p&gt;

&lt;p&gt;You can try to run your test in Visual Studio.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidk9498cop8oomk7s1h0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidk9498cop8oomk7s1h0.png" alt="VS Run tests" width="769" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now for the interesting part. We will create a new repository on Gitea and link it to the project in Visual Studio. After that, we simply push the project to Gitea. With the foundation in place, we can start writing the action to run the automated testing.&lt;/p&gt;

&lt;p&gt;All actions must be stored in the &lt;code&gt;.gitea/workflows&lt;/code&gt; folder in our repository. Actions are written in &lt;em&gt;YAML&lt;/em&gt; format, and any file with the yaml suffix placed in &lt;code&gt;.gitea/workflows/&lt;/code&gt; will be automatically executed.&lt;/p&gt;

&lt;p&gt;Let's create the following action, name it for example &lt;code&gt;nunit_test.yaml&lt;/code&gt;, and save it in the &lt;code&gt;.gitea/workflows/&lt;/code&gt; directory of the repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0yjoodobsyc8taydc5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0yjoodobsyc8taydc5o.png" alt="Gitea new workflow action" width="545" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Testing Example&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-and-test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup dotnet&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8.0.x'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build app&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build -c Release --no-restore&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run automated tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test -c Release --no-build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line &lt;code&gt;name: Testing Example&lt;/code&gt; is the name of the workflow. We can name the workflow action as it suits us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;             &lt;span class="c1"&gt;# The trigger for this workflow.&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="c1"&gt;# Push event, it is run whenever someone makes a push.&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# Filter for a specific branche. You can skip it if you want to run action for each branche.&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;jobs:&lt;/code&gt; section represents a group of tasks that will run sequentially. In this case, the job is named &lt;strong&gt;&lt;em&gt;build-and-test&lt;/em&gt;&lt;/strong&gt;. The &lt;code&gt;runs-on:&lt;/code&gt; attribute specifies the operating system for the job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps Breakdown
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check out the repository code:&lt;/strong&gt; It's always a good idea to check out the source code of your repository within your workflow at the beginning.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out repository code&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up the .NET SDK environment:&lt;/strong&gt; Ensure the correct version of the .NET SDK is installed for the next steps. Specify your required version(s).
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup dotnet&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v3&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8.0.x'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Restore dependencies:&lt;/strong&gt; Execute shell commands directly from the script using the &lt;strong&gt;&lt;em&gt;run&lt;/em&gt;&lt;/strong&gt; command. The &lt;code&gt;dotnet restore&lt;/code&gt; command will ensure that all required dependencies and NuGet packages are downloaded and restored.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restore dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet restore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build the application:&lt;/strong&gt; Build the source code to check if the compiler finds any errors in the code.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run automated tests&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet build -c Release --no-restore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the automated tests:&lt;/strong&gt; Execute the tests you wrote.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run automated tests&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test -c Release --no-build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enhancing the Workflow
&lt;/h3&gt;

&lt;p&gt;To improve the workflow, we can log the test results and upload them as an artifact. Rewrite the test execution and add another step to the job, specifying your own path to the generated file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate test report&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test -c Release --no-build --logger "html;logfilename=test_results.html"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload report as artifact&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-reports&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ gitea.workspace }}/TestProject1/TestResults/test_results.html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Workflow Action
&lt;/h2&gt;

&lt;p&gt;If we have both the &lt;strong&gt;&lt;em&gt;console project&lt;/em&gt;&lt;/strong&gt; and the &lt;strong&gt;&lt;em&gt;test project&lt;/em&gt;&lt;/strong&gt; stored in the Gitea repository and our workflow action is ready, the next step is to test it. Let's make a change to our code, commit the changes, and push them to the &lt;code&gt;master&lt;/code&gt; branch of the repository. Then, in the Gitea web interface, we will see the action running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m167835ckbmbnp7zzg5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7m167835ckbmbnp7zzg5.png" alt="Gitea run workflow action" width="543" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With Gitea up and running, we added Docker to our setup to facilitate containerized environments, which streamline development and deployment processes. We then configured a Gitea Actions runner using Docker, allowing us to automate our build and test workflows. To demonstrate of this setup, we created a simple console application in Visual Studio, wrote a basic MyMath class, and then added an NUnit Test Project to test our code. We crafted a Gitea workflow action to automatically build and test our application whenever changes are pushed to the repository. By following these steps, we have created self-hosted Git server environment that supports automated testing and continuous integration.&lt;/p&gt;

&lt;p&gt;With your Gitea server fully operational, you can now enjoy the benefits of a self-hosted Git solution, customize it to fit your team's needs, and continue to expand its capabilities with additional workflows and integrations. &lt;em&gt;May your code bring you joy!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For more details on configuring Gitea and other settings, be sure to check out my article &lt;a href="https://dev.to/mortylen/easy-self-hosted-git-installation-on-ubuntu-server-2o4c"&gt;Gitea Self-Hosted Action Ubuntu Server&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you found this useful, consider supporting me:&lt;/p&gt;

&lt;p&gt;☕ &lt;a href="https://buymeacoffee.com/mortylen" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;👉 &lt;em&gt;My github profile &lt;a href="https://github.com/mortylen" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
👉 &lt;em&gt;My blog page &lt;a href="https://mortylen.hashnode.dev/" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
📷 &lt;em&gt;Cover photo by&lt;/em&gt; &lt;a href="https://unsplash.com/photos/a-close-up-of-a-text-description-on-a-computer-screen-842ofHC6MaI" rel="noopener noreferrer"&gt;Yancy Min&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>cicd</category>
      <category>git</category>
      <category>gitea</category>
      <category>workflow</category>
    </item>
    <item>
      <title>Easy Self-Hosted Git Installation on Ubuntu Server</title>
      <dc:creator>mortylen</dc:creator>
      <pubDate>Wed, 01 May 2024 15:27:07 +0000</pubDate>
      <link>https://forem.com/mortylen/easy-self-hosted-git-installation-on-ubuntu-server-2o4c</link>
      <guid>https://forem.com/mortylen/easy-self-hosted-git-installation-on-ubuntu-server-2o4c</guid>
      <description>&lt;p&gt;Welcome to a quick guide detailing the installation and setup of a self-hosted git server designed for beginners. We'll start by installing Ubuntu server, a popular choice known for its ease of use. Next, we'll set up PostgreSQL, a powerful and reliable open-source database management system, to ensure seamless data management for our git server. Finally, we'll integrate Gitea, a lightweight self-hosted git service, enabling you to effortlessly host your repositories and collaborate with your team members. For advanced configurations, detailed manuals for each service will be referenced later in the text. Let's proceed, step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ubuntu Server
&lt;/h2&gt;

&lt;p&gt;To begin, we'll need an operating system. For this guide, Ubuntu Server is our choice. If you already have a Linux system installed, feel free to skip this step. Installing Ubuntu Server is straightforward—simply follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Download the Ubuntu Server ISO image from the official website: &lt;a href="https://ubuntu.com/download/server" rel="noopener noreferrer"&gt;https://ubuntu.com/download/server&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a bootable drive and proceed with the installation, or create a new virtual machine from the ISO file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the server according to your preferences. Detailed installation instructions can be found on the official website: &lt;a href="https://ubuntu.com/tutorials/install-ubuntu-server" rel="noopener noreferrer"&gt;https://ubuntu.com/tutorials/install-ubuntu-server&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For this tutorial I have chosen the following settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: English&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard&lt;/strong&gt;: English (US)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The base for the installation&lt;/strong&gt;: Ubuntu Server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network&lt;/strong&gt;: Choose according to your own preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage and file system&lt;/strong&gt;: Choose according to your own preferences&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile setup&lt;/strong&gt;: Choose your name and password

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;name&lt;/strong&gt;: administrator&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;server name&lt;/strong&gt;: git_server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;password&lt;/strong&gt;: choose your strong password.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenSSH&lt;/strong&gt;: Install OpenSSH server&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;After installing the server, log in with your login to the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Before we proceed with installing Gitea, it's essential to set up the database. Gitea supports various databases, including PostgreSQL, MySQL, MariaDB, and MSSQL. In this guide, we'll focus on installing and configuring PostgreSQL on the local server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, ensure your system is up to date:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo apt update&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install PostgreSQL and its additional components:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo apt install postgresql postgresql-contrib&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo apt install postgresql&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable PostgreSQL to start automatically on system boot:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo systemctl enable postgresql.service&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check the status of the PostgreSQL service:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo systemctl status postgresql.service&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbosxncl8ikb01kmnljyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbosxncl8ikb01kmnljyy.png" alt="PostgreSQL service status" width="752" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Switch to the postgres user:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo su - postgres&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;View the &lt;strong&gt;&lt;em&gt;postgresql.conf&lt;/em&gt;&lt;/strong&gt; file to check the '&lt;strong&gt;&lt;em&gt;password_encryption&lt;/em&gt;&lt;/strong&gt;' attribute. You can use a text editor like &lt;strong&gt;nano&lt;/strong&gt;. Find the path to the configuration file using the '&lt;strong&gt;&lt;em&gt;SHOW config_file&lt;/em&gt;&lt;/strong&gt;' command.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ psql -c 'SHOW config_file'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ nano [FILE-NAME]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Ensure '&lt;strong&gt;&lt;em&gt;password_encryption&lt;/em&gt;&lt;/strong&gt;' is set to '&lt;strong&gt;&lt;em&gt;scram-sha-256&lt;/em&gt;&lt;/strong&gt;':&lt;/p&gt;

&lt;p&gt;&lt;code&gt;password_encryption = scram-sha-256&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart PostgreSQL to apply the changed settings:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ systemctl restart postgresql&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Access the PostgreSQL terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ psql&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a database user. Replace '&lt;strong&gt;&lt;em&gt;mypassword&lt;/em&gt;&lt;/strong&gt;' with a strong password. &lt;em&gt;Don't forget the semicolon at the end&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CREATE ROLE gitea WITH LOGIN PASSWORD 'mypassword';&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fri3f5c9lj6v6h4yapuiz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fri3f5c9lj6v6h4yapuiz.png" alt="Create role" width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create the '&lt;strong&gt;&lt;em&gt;giteadb&lt;/em&gt;&lt;/strong&gt;' database. Later, you connect to the database using the name of that database and the name of the user created above:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CREATE DATABASE giteadb WITH OWNER gitea TEMPLATE template0 ENCODING UTF8 LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8';&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exit the PostgreSQL terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\q&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stay logged in with '&lt;strong&gt;&lt;em&gt;postgres&lt;/em&gt;&lt;/strong&gt;' user and add an authentication rule to the '&lt;strong&gt;&lt;em&gt;pg_hba.conf&lt;/em&gt;&lt;/strong&gt;' file. You can use a text editor like &lt;strong&gt;nano&lt;/strong&gt;. Find the path to the configuration file using the '&lt;strong&gt;&lt;em&gt;SHOW hba_file&lt;/em&gt;&lt;/strong&gt;' command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ psql -c 'SHOW hba_file'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ nano [FILE-NAME]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Add the following line, where '&lt;strong&gt;&lt;em&gt;giteadb&lt;/em&gt;&lt;/strong&gt;' is the database name and '&lt;strong&gt;&lt;em&gt;gitea&lt;/em&gt;&lt;/strong&gt;' is the user name:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;local giteadb gitea scram-sha-256&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart PostgreSQL to apply the changed settings:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ systemctl restart postgresql&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test the connection to the database, where '&lt;strong&gt;&lt;em&gt;gitea&lt;/em&gt;&lt;/strong&gt;' is the user name and '&lt;strong&gt;&lt;em&gt;giteadb&lt;/em&gt;&lt;/strong&gt;' is the database name:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ psql -U gitea -d giteadb&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6dcmirmxf4ig11k5iek.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn6dcmirmxf4ig11k5iek.png" alt="Test database connection" width="410" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Exit the PostgreSQL terminal and switch user with a user with sudo privileges:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;\q&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ su - administrator&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Gitea
&lt;/h2&gt;

&lt;p&gt;For self-hosted git I chose Gitea. It's a powerful tool for smaller teams that provides many advanced features such as Git hosting, code review, team collaboration, CI/CD, and more. For a detailed feature comparison, visit &lt;a href="https://docs.gitea.com/installation/comparsion" rel="noopener noreferrer"&gt;https://docs.gitea.com/installation/comparsion&lt;/a&gt;. Additionally, you'll find a comprehensive installation guide there.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install Gitea using wget. Replace the URL with the desired version. We recommend opting for the latest version. Here's an example for 64-bit Linux, version 1.22. For the full list, visit &lt;a href="https://dl.gitea.com/gitea/" rel="noopener noreferrer"&gt;https://dl.gitea.com/gitea/&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ wget -O gitea https://dl.gitea.com/gitea/1.22/gitea-1.22-linux-amd64&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ chmod +x gitea&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Verify if Git is installed:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ git --version&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi9s17z7n7ixj1sn152j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi9s17z7n7ixj1sn152j.png" alt="Gitea version" width="380" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a user for Gitea:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf5vd9rd25043u5lspuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbf5vd9rd25043u5lspuy.png" alt="Create user" width="800" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set up the directory structure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ mkdir -p /var/lib/gitea/{custom,data,log}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ chown -R git:git /var/lib/gitea/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ chmod -R 750 /var/lib/gitea/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ mkdir /etc/gitea&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ chown root:git /etc/gitea&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ chmod 770 /etc/gitea&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy Gitea to a global location:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo cp gitea /usr/local/bin/gitea&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a system service. Download the file and save it to &lt;code&gt;/etc/systemd/system/&lt;/code&gt; or view the &lt;code&gt;raw&lt;/code&gt; file in a browser and replace the URL with the version of Gitea you installed. You can find the list on &lt;a href="https://github.com/go-gitea/gitea/blob/release/v1.22/contrib/systemd/gitea.service" rel="noopener noreferrer"&gt;https://github.com/go-gitea/gitea/blob/release/v1.22/contrib/systemd/gitea.service&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo wget https://raw.githubusercontent.com/go-gitea/gitea/release/v1.22/contrib/systemd/gitea.service -P /etc/systemd/system/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable automatic start of the service:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ sudo systemctl enable gitea --now&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvpi2b3atarys5mwrdwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvpi2b3atarys5mwrdwk.png" alt="Enable gitea service" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you have a problem running the service, check the contents of the file: `/etc/systemd/system/gitea.service`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Verify the status of the Gitea service:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ systemctl status gitea&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Logging in to the Gitea Web Interface
&lt;/h2&gt;

&lt;p&gt;If the installation was successful and the gitea service is running, it's time to log in to Gitea via the web interface. Launch the web browser and enter the &lt;strong&gt;&lt;em&gt;IP&lt;/em&gt;&lt;/strong&gt; of your new git server and port &lt;strong&gt;&lt;em&gt;3000&lt;/em&gt;&lt;/strong&gt; in the address field. You should see the &lt;strong&gt;&lt;em&gt;Initial Configuration&lt;/em&gt;&lt;/strong&gt; page.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;192.168.52.129:3000&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Configuration - Database Settings
&lt;/h3&gt;

&lt;p&gt;In this section we will configure the connection between git and your PostgreSQL database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ffi01yfhxvb6or9qy1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ffi01yfhxvb6or9qy1c.png" alt="Database settings" width="718" height="466"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database Type&lt;/strong&gt;: Select &lt;code&gt;PostgreSQL&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Host&lt;/strong&gt;: Since we have installed the database as local, we choose local IP and default port for PostgreSQL. &lt;code&gt;127.0.0.1:5432&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Username&lt;/strong&gt;: The PostgreSQL database user (role) we created. In this case &lt;code&gt;gitea&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Password&lt;/strong&gt;: Password for the PostgreSQL database user we created. In this case &lt;code&gt;mypassword&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database Name&lt;/strong&gt;: The name of the database we created. In this case &lt;code&gt;giteadb&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSL&lt;/strong&gt;: Securing the communication between the gitea and the PostgreSQL database. In this case &lt;code&gt;Disable&lt;/code&gt; since the database server is installed locally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema&lt;/strong&gt;: Leave blank, the database is in the default &lt;code&gt;Public&lt;/code&gt; scheme.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initial Configuration - General Settings
&lt;/h3&gt;

&lt;p&gt;Gitea configuration. We can use the default settings, or change them according to our needs. It is worth mentioning the path to the repositories, LFS and logs. Change to a more reasonable repository if necessary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fziemdnxluehyhud1exmh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fziemdnxluehyhud1exmh.png" alt="General settings" width="762" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Configuration - Optional Settings
&lt;/h3&gt;

&lt;p&gt;In this section it is possible to set up a mailer for gitea, set up a server and third party service and set up an administrator account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3g50xxncsne8zkry17rg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3g50xxncsne8zkry17rg.png" alt="Optional settings" width="395" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this quick tutorial we can leave it in the default settings and click the &lt;code&gt;Install Gitea&lt;/code&gt; button. All settings can be changed later in the Gitea configuration file &lt;code&gt;/etc/gitea/app.ini&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuaqjluumknpujn8uyfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvuaqjluumknpujn8uyfp.png" alt="Install Gitea button" width="416" height="74"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Sign In to Gitea
&lt;/h3&gt;

&lt;p&gt;If you have created an administrator account in &lt;strong&gt;&lt;em&gt;Initial Configuration - Optional Settings&lt;/em&gt;&lt;/strong&gt;, you can now log in with it. If not, register a new account. The first user registered to Gitea becomes the admin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8besyg732v3yynv8ygb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz8besyg732v3yynv8ygb.png" alt="Sign in to gitea" width="580" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it, your self-hosted git server is ready to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Test Your Self-Hosted Git Using MS Visual Studio.
&lt;/h2&gt;

&lt;p&gt;We have no choice but to try git server. First we create a new test repository in Gitea and then link it to the project in Visual Studio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create New Repository
&lt;/h3&gt;

&lt;p&gt;After logging into Gitea, we create a new repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf8juv9xci1vr2t44a1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpf8juv9xci1vr2t44a1i.png" alt="Create repository" width="556" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in all the settings according to your preferences and click the &lt;code&gt;Create Repository&lt;/code&gt; button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Studio and Git
&lt;/h3&gt;

&lt;p&gt;First, we'll copy the URL link from our new test repository in Gitea.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyvi047fiwuo2cq6ile1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqyvi047fiwuo2cq6ile1.png" alt="Repository url link" width="728" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now in the Visual Studio project, go to the &lt;code&gt;Git&lt;/code&gt; tab and select &lt;code&gt;Create Git repository...&lt;/code&gt;. On the left hand side under the &lt;code&gt;Other&lt;/code&gt; category, select &lt;code&gt;Existing remote&lt;/code&gt;. In the &lt;code&gt;Push your code to an existing remote&lt;/code&gt; section, paste your copied URL link and click on &lt;code&gt;Create and Push&lt;/code&gt;. Visual Studio prompts you for a username and password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjs18miefaf5py8oq5qq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjs18miefaf5py8oq5qq.png" alt="Visual Studio repository" width="750" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;View&lt;/code&gt; tab, select &lt;code&gt;Git Changes&lt;/code&gt;, using this window you can work with the linked git. If &lt;code&gt;Commit&lt;/code&gt; doesn't work, you probably don't have a user name set. Check this in &lt;code&gt;Git -&amp;gt; Settings&lt;/code&gt;. In Visual Studio 2022 I noticed a small bug when VS had to be restarted, otherwise it didn't allow me to commit changes even if I had the user set up.&lt;/p&gt;

&lt;p&gt;Now you should be able to see all your commits in your git repository in Gitea using the web browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this simple tutorial, I tried to show you how to install git server using Gitea in a simple step-by-step way. It was installed on an Ubuntu server using PostgreSQL as the datastore. I didn't go into any detailed setup of Linux, the database server or Gitea itself, the goal was to show how easy it is to run a self-hosted git server. I have tested all the steps in the tutorial in a virtual environment, there may be slight changes depending on the versions of each component installed. I hope you have fun discovering the new features.&lt;/p&gt;

&lt;p&gt;👀 In another article I wrote a little bit about workflow actions. If you would like to run Continuous Integration on Gitea, see here: &lt;a href="https://dev.to/mortylen/gitea-self-hosted-workflow-action-for-ci-1bhf"&gt;Gitea Self-Hosted Workflow Action For CI&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If you found this useful, consider supporting me:&lt;/p&gt;

&lt;p&gt;☕ &lt;a href="https://buymeacoffee.com/mortylen" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;My github profile &lt;a href="https://github.com/mortylen" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;My blog page &lt;a href="https://mortylen.hashnode.dev/" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;&lt;/em&gt;&lt;br&gt;
&lt;em&gt;Cover Photo by &lt;a href="https://unsplash.com/@scottrodgerson?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Scott Rodgerson&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-bunch-of-blue-wires-connected-to-each-other-PSpf_XgOM5w?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>git</category>
      <category>ubuntu</category>
      <category>postgres</category>
      <category>visualstudio</category>
    </item>
  </channel>
</rss>
