<?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>DEV Community: Jim McKeon</title>
    <description>The latest articles on DEV Community by Jim McKeon (@mckeondev).</description>
    <link>https://dev.to/mckeondev</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3824646%2F2622ca35-d934-4b47-8267-614c73a7029d.jpeg</url>
      <title>DEV Community: Jim McKeon</title>
      <link>https://dev.to/mckeondev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mckeondev"/>
    <language>en</language>
    <item>
      <title>Better Problems: The Power of Abstraction</title>
      <dc:creator>Jim McKeon</dc:creator>
      <pubDate>Mon, 20 Jul 2026 22:37:13 +0000</pubDate>
      <link>https://dev.to/mckeondev/better-problems-the-power-of-abstraction-1dei</link>
      <guid>https://dev.to/mckeondev/better-problems-the-power-of-abstraction-1dei</guid>
      <description>&lt;p&gt;&lt;em&gt;Second in a series on practical software design.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Technical details are essential, but they're not the solution&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Which is clearer?
&lt;/h3&gt;

&lt;p&gt;Here's a simple user story for a lending library api:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a library member, I want to borrow a book so that I can take it home.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And here are two code blocks that implement it.  Take a few seconds to look at each. Which is easier to understand?&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"borrow"&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;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Borrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LibraryDb"&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;var&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;connectionString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&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;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&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="s"&gt;"SELECT * FROM Books WHERE Id = @id"&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="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&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="n"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&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;ExecuteReaderAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;Book&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&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="k"&gt;await&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;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;book&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;Book&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;book&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="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&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="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&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;"Title"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DueDate&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;"DueDate"&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;DateTime&lt;/span&gt;&lt;span class="p"&gt;?;&lt;/span&gt;
        &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CheckedOutByMemberId&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;"CheckedOutByMemberId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&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="nf"&gt;Close&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;book&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&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="nf"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// check if already checked out&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;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CheckedOutByMemberId&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CheckedOutByMemberId&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;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;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"already out"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// now get the member fines&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;fines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cmd2&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="s"&gt;"SELECT Amount FROM Fines WHERE MemberId = @m AND Paid = 0"&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="n"&gt;cmd2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cmd2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteReaderAsync&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsync&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fines&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;reader2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;reader2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&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;fines&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;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;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"has fines of "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// set due date to 2 weeks from now&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;due&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cmd3&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="s"&gt;"UPDATE Books SET DueDate = @d, CheckedOutByMemberId = @m WHERE Id = @id"&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="n"&gt;cmd3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;due&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cmd3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cmd3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&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="n"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cmd3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// add to member checkout list&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cmd4&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="s"&gt;"INSERT INTO MemberCheckouts (MemberId, BookId, CheckedOutOn) VALUES (@m, @b, @now)"&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="n"&gt;cmd4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cmd4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cmd4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@now"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cmd4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteNonQueryAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"borrowed"&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"borrow"&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;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Borrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bookId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_books&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookId&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;book&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;NotFound&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;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCheckedOut&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Conflict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Book is already checked out."&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_members&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasOutstandingFines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Outstanding fines must be cleared before borrowing."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetDueDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoanPeriodDays&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_bookCheckoutService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckOutBook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memberId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're like me, you chose the second. Why? It's easier to parse because there's less information in it (also, it uses more intuitive variable and method names, but that's for another post). We have a limited capacity for detail, and usually only want enough to answer the question in front of us. Past that point, our capacity for analysis and critical thinking deteriorates. &lt;strong&gt;Less information&lt;/strong&gt; in this case is always better. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Case Against Detail
&lt;/h3&gt;

&lt;p&gt;The second method is clearer because it &lt;em&gt;abstracts&lt;/em&gt; the technical details that are in the first. The terms "abstracting" and "abstraction" are omnipresent in design literature: DDD, Clean Architecture, SOLID, etc., all assume you’re comfortable with it, but none of them describe how it works or how to get fluent in it.&amp;nbsp; And despite being foundational, it's &lt;a href="https://www.academia.edu/49858167/Defining_the_Competence_of_Abstract_Thinking_and_Evaluating_CS_Students_Level_of_Abstraction" rel="noopener noreferrer"&gt;rarely taught explicitly&lt;/a&gt;. CS programs teach the language mechanisms of abstraction — abstract classes, interfaces, ADTs — but not how to &lt;em&gt;apply it&lt;/em&gt; to requirements analysis or software design.&lt;/p&gt;

&lt;p&gt;So we've seen how code that abstracts technical details is easier to process. But readability is the downstream benefit, the result of well designed software. The same act of substituting a symbol for a mess of detail does something more valuable earlier, when the code doesn't exist yet and you're building an understanding of the problem.&lt;/p&gt;

&lt;p&gt;When you replace "open a connection, run this query, map these columns" with repository.GetSomething(), you're not just hiding detail from a future reader: you're &lt;strong&gt;deliberately hiding it from yourself&lt;/strong&gt;, so your working memory is spent on the shape of the solution rather than the mechanics of data access. This is the real purpose of encapsulation. It gets taught as a mechanism but the reason it matters is almost never stated: holding complexity at bay so a mind can work above it.&lt;/p&gt;

&lt;h3&gt;
  
  
  You Already Abstract
&lt;/h3&gt;

&lt;p&gt;This shift from trees to forest-first thinking can be hard for developers, but your mind already does it instinctively. Consider this: a friend just texted asking if you can meet for lunch tomorrow. Start assessing your schedule.&lt;/p&gt;

&lt;p&gt;Most likely your thoughts didn't follow a sequence like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;open eyes&lt;/li&gt;
&lt;li&gt;hit snooze&lt;/li&gt;
&lt;li&gt;stretch, yawn, rub eyes&lt;/li&gt;
&lt;li&gt;move one leg to the edge of the bed …&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You automatically grouped the morning into a single block — "get up, get ready" — then checked it against the commitments that actually matter: a mid-morning meeting, the lunch window, your afternoon. You abstracted without trying, because processing a day as a hundred discrete physical actions would be paralyzing.&lt;/p&gt;

&lt;p&gt;In effect you answered the question by &lt;strong&gt;skipping the details&lt;/strong&gt;. The minutiae of your morning routine — every step of making coffee, every minute of your commute — aren't just unnecessary, they actively get in the way. They consume the working memory you need for the actual problem: "am I free at noon?"&lt;/p&gt;

&lt;p&gt;This is the same dynamic at work in code. The forty-line version of Borrow() forces you to hold connection strings, query parameters, and column mappings in your head before you can even see the end to end flow of the solution. At that level, the details are noise that obscure it — and worse, they let you make progress on implementation without ever arriving at a viable design. We already know this intuitively about everything except the code we write. The next section is about why.&lt;/p&gt;

&lt;h3&gt;
  
  
  Detail Or Die
&lt;/h3&gt;

&lt;p&gt;The answer is that software development is extraordinarily complex. Just configuring an IDE, getting a debugger attached, or resolving your first merge conflict takes a real investment of time and energy. In the beginning, the details consume you because they have to — they're the price of entry.&lt;/p&gt;

&lt;p&gt;But after you've been writing code for a while, you start to feel the cost. The code is hard to follow, fragile to change, and difficult to debug without breaking something. That frustration is a signal: the detail-first habit that got you through the learning curve is now working against you. This is where learning to abstract starts to pay off.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Abstract
&lt;/h3&gt;

&lt;p&gt;Abstraction is thinking in terms of &lt;strong&gt;what&lt;/strong&gt; happens rather than &lt;strong&gt;how&lt;/strong&gt; it happens. It's holding a technical &lt;em&gt;theme&lt;/em&gt; in your head instead of the mechanics that comprise it.&lt;/p&gt;

&lt;p&gt;To get there, you have to short-circuit your mind's detail-focused default — the reflex to reach for the connection string, declare a variable, calculate a result before you've nailed down the high-level flow. One way to force the switch is to write down, in plain prose, what your code will do before you implement it. To demonstrate, let's construct a solution for the library API user story at the top of the post:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a member, I want to borrow a book so I can take it home.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After giving it some thought, one possible sequence of events is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the book from the app db&lt;/li&gt;
&lt;li&gt;Verify it's not already checked out&lt;/li&gt;
&lt;li&gt;Verify the member's fine balance is $0&lt;/li&gt;
&lt;li&gt;Update the due date on the book&lt;/li&gt;
&lt;li&gt;Add the book to the member's checked-out list&lt;/li&gt;
&lt;li&gt;Save changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the whole operation. Six lines, zero technical details. At this altitude you can hold the entire endpoint in your head at once, because you're thinking in responsibilities, not implementation. &lt;/p&gt;

&lt;p&gt;And because the solution is easy to express, it's cheap to change. Look at the fine-balance check — we verify the member owes nothing, but we never checked whether the member is allowed to borrow another book at all. Most libraries cap how many items you can have out at once. It's a big miss.&lt;/p&gt;

&lt;p&gt;To fix it we add one line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the book from the app db&lt;/li&gt;
&lt;li&gt;Verify it's not already checked out&lt;/li&gt;
&lt;li&gt;Verify the member's fine balance is $0&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify the member is under their borrowing limit&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Update the due date on the book&lt;/li&gt;
&lt;li&gt;Add the book to the member's checked-out list&lt;/li&gt;
&lt;li&gt;Save changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That edit cost a few keystrokes. Had we caught this three hundred lines into the implementation — a new query, a new branch, a new failure path threaded through code that assumed it wasn't there — it would have cost an afternoon, and more likely than not, made the code less readable and flexible. We changed our mind for free because we abstracted first. &lt;/p&gt;

&lt;h3&gt;
  
  
  Learning From Experience
&lt;/h3&gt;

&lt;p&gt;Keeping your eye out for where abstraction might have helped is another good way to build the skill. Often, duplication is the symptom. When you see a significant code block repeated across a class or file, ask yourself: what responsibility does this represent? This applies to new code too — when you're writing something, ask yourself what existing code it resembles (even partially). If the answer is 'a lot of things,' you're looking at a missing abstraction.&lt;/p&gt;

&lt;p&gt;As you accrue experience with this mindset, you'll start noticing shapes that repeat across systems — even systems in completely different domains. For example, a significant portion of CRUD API use cases implement some version of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Validate input -&amp;gt; invoke service -&amp;gt; query data -&amp;gt; apply business rules -&amp;gt; construct response&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These recurring shapes are the building blocks of design. I'll dig into them — what the literature calls &lt;a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2008/august/patterns-in-practice-object-role-stereotypes" rel="noopener noreferrer"&gt;role stereotypes&lt;/a&gt; — in a future post. For now, the point is this: once you start thinking in abstractions, you stop solving every problem from scratch. It's Legos vs a jigsaw puzzle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;The prose-first approach is a starting point — a thinking tool for breaking out of the detail-first habit and working at the right altitude.  In upcoming posts I'll discuss how to use abstraction to &lt;strong&gt;identify responsibilities&lt;/strong&gt;, assign them to &lt;strong&gt;objects&lt;/strong&gt; and incrementally build a &lt;strong&gt;working design&lt;/strong&gt;.  But none of that is possible until you can see the forest first.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>oop</category>
      <category>design</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Better Problems: Design or Debug</title>
      <dc:creator>Jim McKeon</dc:creator>
      <pubDate>Sun, 05 Jul 2026 20:18:01 +0000</pubDate>
      <link>https://dev.to/mckeondev/design-or-debug-2jpf</link>
      <guid>https://dev.to/mckeondev/design-or-debug-2jpf</guid>
      <description>&lt;p&gt;&lt;em&gt;First in a series on practical software design.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Code should document the solution to a problem, not the process of solving it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Familiar Frustration
&lt;/h2&gt;

&lt;p&gt;It’s 4 p.m. on a Friday, and I’ve spent the day working on a new high-priority enhancement. We discussed the requirements over email, chat, video calls, and several standups, and each time I walked away from these interactions reasonably confident that I understood the ask.&lt;/p&gt;

&lt;p&gt;Now I’m deep into the implementation when the realization hits: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait. I didn’t consider that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Maybe I made an assumption that was never actually supported. Maybe I just remembered something that was said last week. Maybe two requirements contradict each other. Whatever the cause, the result is the same: the solution no longer holds, a large portion of the code needs to be rewritten, and I’m already thinking about how I’m going to explain the delay. &lt;/p&gt;

&lt;p&gt;Without a doubt, this experience has been the single largest cause of frustration and stress over the course of my career. And based on my conversations with other developers, it's a very common one. The cause isn't a lack of aptitude, focus, or discipline. It's that I started building without a considered plan. The mistake, the missed dependency, the contradiction — they were always going to surface. The only question was when. And by discovering them at the end of a Friday, with the code already written, I've maximized their destructiveness.&lt;/p&gt;

&lt;p&gt;Things changed when I started hunting these problems down before writing any code. The contradiction or miss is still there, but caught in design, it never becomes a mistake, just a decision to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Time You Don't Think You Have
&lt;/h2&gt;

&lt;p&gt;I've worked in some extremely demanding environments. The work you're handed and the deadline attached to it are, more often than not, in open conflict — the ask underspecified, the timeline set before anyone understood the ask. In cultures like this, design reads as stalling. Time spent thinking is time stolen from actual work.  Questions are unwelcome, because every real one exposes &lt;strong&gt;complexity nobody wanted to admit was there&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Opening VS Code and typing feels like progress. Stepping back to think feels like stalling — worse, it feels like the thing you do when you're not sure what you're doing. &lt;/p&gt;

&lt;p&gt;But here's what those environments taught me: uncertainty and risk are unavoidable. We just get good at hiding them — from the people we report to, and from ourselves. It's a shell game. The people above you don't want to hear why something is hard, or impossible. You don't want to fail. So the unresolved questions get pushed to the back burner, until they boil over — at 4 p.m. on a Friday, in code you've already written, on the deadline you were protecting by not asking.&lt;/p&gt;

&lt;p&gt;Design and debugging are often the same problem-solving work performed at different times. That’s the thing about skipping design: the task doesn’t disappear. It moves, and it grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Wicked Problem
&lt;/h2&gt;

&lt;p&gt;By its nature, software development exceeds our ability to hold every rule, dependency, interaction, and unresolved question in our heads at once. It's been likened to a &lt;a href="https://en.wikipedia.org/wiki/Wicked_problem" rel="noopener noreferrer"&gt;wicked problem&lt;/a&gt;: our understanding of the solution changes as we attempt to solve it, and the solution itself exposes new constraints, contradictions, and unanswered questions. &lt;/p&gt;

&lt;p&gt;Over the course of my career, I’ve learned a few things to be consistently true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nothing is as simple as it seems, no matter how confidently the project manager or product owner insists otherwise.&lt;/li&gt;
&lt;li&gt;Mistakes are inevitable. I will make unsupported assumptions, misunderstand requirements, reverse operands, and misspell variables. The people I depend on for crucial information will make mistakes too.&lt;/li&gt;
&lt;li&gt;A simple word, phrase, or concept can mean different things to different people (I recently sat through a standup discussion of what "send" vs "push" mean).&lt;/li&gt;
&lt;li&gt;Customers will change their minds. More often than not, people do not fully know what they want until they see an example of what they don't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Software is therefore not simply constructed from a complete and stable set of requirements. The software itself becomes a proof of concept that users evaluate, reject, and refine. &lt;/p&gt;

&lt;p&gt;Once we accept this, design becomes more than deciding how to build a feature. It also becomes the process of identifying what might prevent the feature from working: conflicting requirements, hidden dependencies, unsupported assumptions, misunderstood language, and missing information.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Work of Design
&lt;/h2&gt;

&lt;p&gt;I've made the case that design catches problems early. But that's only one of its benefits. Even when nothing goes wrong — no contradiction, no missed dependency — designing first produces better software.&lt;/p&gt;

&lt;p&gt;That's because design is as much an analytical process as a diagnostic one. Working out which behaviors are needed and where they belong breaks one overwhelming problem into smaller, simpler ones. The design is where I think; the code is where I record what I decided. Skip the thinking and the code becomes the place I think — which is why it comes out tangled, reworked in place, shaped by whatever I understood the moment I typed each line rather than by the whole.&lt;/p&gt;

&lt;p&gt;It's also constructive. Intentionally designed code is easier to understand because it maps to how the problem actually decomposes. It's easier to fix, because a change has an obvious place to go. And it's easier to extend, because its structure is modeled on the real process it serves, rather than assembled from whatever shape the code happened to take as I wrote it. Let the structure emerge by accident and you get code that works only for the context it was written in, and resists attempts to change it later.&lt;/p&gt;

&lt;p&gt;Neither benefit here depends on catching defects. They're what you get from thinking about shape before shape is expensive to change. Problem-prevention is the same act seen from the other side — the roadblocks surface while you're deciding how the pieces fit, before any of it reaches code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems Are the Product
&lt;/h2&gt;

&lt;p&gt;Unanticipated problems in software are inevitable. But they don't get in the way of the work, they &lt;strong&gt;are the work&lt;/strong&gt;: elegant design is just a hard problem handled well. Strip out the contradictions and the edge cases and there's nothing left to build, nothing to solve, nothing satisfying about having solved it. Finding those problems early and building them in — as features, not fixes — is the craft. The rest of this series is about finding and shaping those problems before they become disruptive. It's for anyone who senses that planning the work first is the key to doing it better, but doesn't know where to begin.&lt;/p&gt;

&lt;p&gt;In future posts I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Power of Abstraction&lt;/strong&gt;: the implied skill needed to put DDD, Clean Architecture, and SOLID into practice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behaviors and Interactions&lt;/strong&gt;: modeling responsibilities, interfaces, and the collaborations between them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Just Enough Design&lt;/strong&gt;: a lightweight, iterative process for turning uncertainty into working software&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>oop</category>
      <category>design</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Expressive Design: Beyond Primitives</title>
      <dc:creator>Jim McKeon</dc:creator>
      <pubDate>Sun, 15 Mar 2026 02:22:21 +0000</pubDate>
      <link>https://dev.to/mckeondev/the-value-of-value-objects-28p8</link>
      <guid>https://dev.to/mckeondev/the-value-of-value-objects-28p8</guid>
      <description>&lt;p&gt;For years I’ve struggled with how to handle very simple validation scenarios.&lt;/p&gt;

&lt;p&gt;Most systems have identifiers with basic constraints — fixed length, allowed characters, or formatting rules. The problem usually isn’t writing the validation itself, but deciding &lt;strong&gt;where that validation should live&lt;/strong&gt; and ensuring it’s &lt;strong&gt;applied consistently&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider a simple example: suppose our application models an Order, and the system requires that an order ID must be exactly 10 characters long.&lt;/p&gt;

&lt;p&gt;For a long time I would have implemented it like this:&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;Order&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;string&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;Nothing unusual there. Also, until fairly recently I would typically enforce validation using a separate validator:&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;OrderValidator&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;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Id must be 10 characters long"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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="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;Now imagine a service responsible for creating orders:&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;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;OrderValidator&lt;/span&gt; &lt;span class="n"&gt;_validator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderValidator&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;)&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;validator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Order&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;id&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;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// persist order, publish events, etc.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&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;Putting aside for the moment that using &lt;a href="https://enterprisecraftsmanship.com/posts/error-handling-exception-or-result/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;exceptions for control flow&lt;/a&gt; isn’t ideal, this approach works, but it comes with a few undesirable side effects:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Any code responsible for creating or updating an Order needs to know that this validator exists.&lt;/li&gt;
&lt;li&gt; The rules for a valid order ID are disconnected from the value they describe.&lt;/li&gt;
&lt;li&gt; Invalid orders can exist until validation is explicitly invoked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Also, what makes two orders equal in this case isn’t obvious. One possible fix is to implement equality directly on the Order entity:&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;Order&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEquatable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&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;string&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&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;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&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="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&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;other&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&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;other&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;Id&lt;/span&gt;&lt;span class="p"&gt;;&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;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&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;override&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&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;Now we can compare orders using the Equals() method, but not with the == and != operators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behold the Value Object
&lt;/h2&gt;

&lt;p&gt;Although I haven’t read the entire book (yet), I’m a big fan of Domain-Driven Design. One of its core concepts is the &lt;a href="https://martinfowler.com/bliki/ValueObject.html" rel="noopener noreferrer"&gt;Value Object&lt;/a&gt;. According to Eric Evans, a value object is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“An object that represents a descriptive aspect of the domain with no conceptual identity.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An easy way to think about value objects is that they behave like primitives such as string or int, but enforce business rules.&lt;/p&gt;

&lt;p&gt;In our example, we could introduce an OrderId type:&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;struct&lt;/span&gt; &lt;span class="nc"&gt;OrderId&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThrowIfNullOrWhiteSpace&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="k"&gt;nameof&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="k"&gt;if&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="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Id must be 10 characters long"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;nameof&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="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;Value&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="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&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;string&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&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;Then the Order class becomes something like this:&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;Order&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IEquatable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&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;id&lt;/span&gt;&lt;span class="p"&gt;;&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;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&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="nf"&gt;ReferenceEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&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;other&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&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;other&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="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetHashCode&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;Id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;();&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;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;obj&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="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Order&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;Now there's no need for an OrderValidator, and an Order &lt;strong&gt;can't be instantiated&lt;/strong&gt; with an invalid id. We can also add == and != operators so that orders compare naturally based on their Id.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;p&gt;By pushing validation into the type system, this approach yields a few practical benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No more duplicated validation rules&lt;/li&gt;
&lt;li&gt;Order identity is easy to find and understand because it's in one place&lt;/li&gt;
&lt;li&gt;The type system now clearly communicates intent: OrderId is no longer "just a string", it's a &lt;strong&gt;fundamental concept&lt;/strong&gt; like a DateTime or a class. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As more value objects are added, the application starts to resemble a self-contained, expressive framework rather than a collection of .NET primitives and utility methods.&lt;/p&gt;

&lt;p&gt;As a corollary to #3 above, I'm a huge proponent of DDD's concept of "Ubiquitous Language".  It forces everyone - project managers, subject matter experts, developers - to communicate in business-focused terms, rather than "tables" and "constraints" and "functions". There's many upsides to this approach, but the most consequential might be that it leads to fewer &lt;strong&gt;missed requirements&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Value objects are a small change in design, but they can have a huge impact. When rules are enforced by the type system, duplication disappears, invalid states become harder to create, and the application becomes easier to understand and navigate. Even simple value objects like OrderId can significantly improve the clarity and safety of a system.&lt;/p&gt;

&lt;p&gt;I’ve only scratched the surface of value objects here. In future posts, I’ll explore other practical techniques for writing software that’s easier to read, easier to reason about, and easier to change. If this topic interests you, I highly recommend &lt;em&gt;Implementing Domain-Driven Design&lt;/em&gt; by Vaughn Vernon, which explores these ideas in much greater depth.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
