<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Lex Parsimonae</title>
	<atom:link href="http://blog.componentoriented.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.componentoriented.com</link>
	<description>In search of simple software</description>
	<lastBuildDate>Mon, 09 Mar 2026 01:05:32 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>
<site xmlns="com-wordpress:feed-additions:1">4570961</site>	<item>
		<title>Accounting for Batch Operations</title>
		<link>http://blog.componentoriented.com/2026/03/accounting-for-batch-operations/</link>
					<comments>http://blog.componentoriented.com/2026/03/accounting-for-batch-operations/#respond</comments>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Mon, 09 Mar 2026 01:05:32 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2619</guid>

					<description><![CDATA[Admit it: you’ve got batch operations.&#160; Although many of us have trained ourselves to think in terms of real-time events acting on individual records, somewhere in your organization, in places you don’t want to talk about at parties, you want them in your enterprise; you need them in your enterprise.&#160; We use words like transaction, &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2026/03/accounting-for-batch-operations/" class="more-link">Continue reading<span class="screen-reader-text"> "Accounting for Batch Operations"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>Admit it: you’ve got batch operations.&nbsp; Although many of us have trained ourselves to think in terms of real-time events acting on individual records, somewhere in your organization, in places you don’t want to talk about at parties, you want them in your enterprise; you <em>need </em>them in your enterprise.&nbsp; We use words like transaction, workflow, error-handling; we use these words as the backbone of a lifetime supporting production applications.</p>



<p>Ok, ok - I’m not going to do the whole monologue, but you get the idea.&nbsp; Where these processes exist, they’re likely deep in the stack of your enterprise, very possibly connecting you to partners you might not have been able to convince to move to your state-of-the-art real-time API backbone.&nbsp; And when they break, impacts can be large.&nbsp; Worse yet, if they fail silently or fail to process all the records they’re supposed to, the effects of these failures can go unnoticed for a while, and the resulting mess can be difficult to clean up.</p>



<h2 class="wp-block-heading">Setting up the sample</h2>



<p>As much as this article is about illustrating important processing guidelines and techniques, I’m also going to chronicle my use of AI tools to construct the example – showing prompts and responses for steps along the way.&nbsp; I’ll start with repo creation, using ChatGPT for this one:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>I want to create a repo to illustrate techniques to track inputs vs. outputs in batch operations, ensuring that no records are lost to errors or data leakage during processing. Suggest a suitable repo name.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I’ve stored prompts and <a href="https://github.com/dlambert-personal/batch-lineage-patterns/blob/main/ai-history/repo-naming-suggestion.md">responses </a>in the accompanying <a href="https://github.com/dlambert-personal/batch-lineage-patterns">github repo</a> for many of these examples.  I’ll link them in so you can review details if you choose.  Following the naming prompt (I chose “batch-lineage-patterns” for the repo name), ChatGPT had some suggestions for next steps.  Again, the full responses are recorded in my repo.  Some of these are pretty good; others probably won’t fit with my intent for this illustration.</p>



<p>For the next step, I dropped into VS Code and began laying out an overall scenario to illustrate, <a href="https://github.com/dlambert-personal/batch-lineage-patterns/blob/main/ai-history/file-gen-initial.md">asking copilot</a> to generate a file to populate a Sqlite DB and another file to serve as the example file for updates:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Generate two files to illustrate a fictional scenario.  In this fictional scenario, an automotive parts retailer is going to receive a file from a supplier with car part inventory and pricing information.  Synthesize sample data to illustrate error handling in processing a batch update file.  Synthetic data needs to include the following:</p>



<ul class="wp-block-list">
<li>Sqlite data to simulate the master data of the retailer.&nbsp; Fields should include partId, vendorName, partName, cost, availableQty, listPrice</li>



<li>CSV update file from supplier.&nbsp; Fields should include partId, vendorName, partName, supplierPrice, availableQty</li>
</ul>



<p>The sample data set should include 100 rows in the CSV.&nbsp; Of the 100 rows, 95 should be legally parsable (data valid per field names and data types).&nbsp; Five rows should have bad data - an extra field, or a missing field, or an improperly escaped comma in a name field, for instance.&nbsp; For the 95 rows that are parsable, 75 should map to parts found in the Sqlite master data.&nbsp; The remaining 20 should be parts that aren’t found in the master file so that a lookup error occurs.</p>



<p>Generate a SQL file for the master data load suitable for use in this type of loading in application startup:<br>context.Database.ExecuteSqlRaw(sqlScript)</p>



<p>Also generate a CSV file to show the batch data update described above.</p>
</blockquote>



<p>I saved the resulting files - they’re <a href="https://github.com/dlambert-personal/batch-lineage-patterns/tree/main/ai-history/initial-data-files">also in github</a>.  My evaluation?  Not bad for a quick prompt, and in fact, the results are probably sufficient for the illustration I’ve got in mind.  Having seen the output, there’s room for some additional scenario specification, and for a more durable use case, I’d go back and reinvest in the specs.</p>



<h2 class="wp-block-heading">Introducing Filehelpers</h2>



<p>The batch-handling concepts here are technology-agnostic, but one package I’ve used with great success in the past will help illustrate some of the best practices I prefer.  <a href="https://www.filehelpers.net/">Filehelpers.net</a> is a nuget package that makes flat-file handling child’s play.  For this example, we’ll start with a simple CSV use case:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Following filehelpers quick start guide (https://www.filehelpers.net/quickstart/), create a mapping class for supplier_update.csv - call the class <a href="http://supplierupdate.cs">SupplierUpdate.cs</a>.</p>
</blockquote>



<p>The mapping class, as you can see, tracks cleanly to the fields in the CSV. Attributes allow formatting and validating fields on read and write - all very easy to extend if needed.</p>



<pre class="wp-block-code"><code>using FileHelpers;

namespace filehelpers_examples
{
    // mapping class generated based on supplier_update.csv
    // follows FileHelpers quick start conventions
    &#91;DelimitedRecord(",")]
    &#91;IgnoreFirst] // csv file contains a header row
    public class SupplierUpdate
    {
        // note: field names match header columns exactly (case-sensitive mapping not required)
        public int partId;
        public string? vendorName;
        public string? partName;
        public decimal supplierPrice;
        public int availableQty;
    }
}
</code></pre>



<p>To use the mapping class, just create an engine of that type:<br></p>



<pre class="wp-block-code"><code>using FileHelpers;
using filehelpers_examples;

var engine = new FileHelperEngine&lt;SupplierUpdate>();
var result = engine.ReadFile("../../../supplier_update.csv")</code></pre>



<p>If you run the sample at this point, you’ll see a ConvertException - something’s gone awry:<br><br><strong>Exception has occurred: CLR/FileHelpers.ConvertException</strong></p>



<p>An unhandled exception of type 'FileHelpers.ConvertException' occurred in FileHelpers.dll: 'Error Converting 'Many' to type: 'Decimal'. '</p>



<p>If you’ve worked with batch files, you’re no doubt familiar with this sort of exception.  Something’s not right somewhere in that hundred-row input file, but where?  Filehelpers can give us some help here:</p>



<pre class="wp-block-code"><code>var engine = new FileHelperEngine&lt;SupplierUpdate>();
// Switch error mode on
engine.ErrorManager.ErrorMode = ErrorMode.SaveAndContinue;
var result = engine.ReadFile("../../../supplier_update.csv");
if (engine.ErrorManager.HasErrors)
    engine.ErrorManager.SaveErrors("../../../supplier_update.errors.out");</code></pre>



<p>Now, when we run, not only are we able to load all the good records successfully, we also now have a file with real descriptions of the errors found:</p>



<pre class="wp-block-code"><code>FileHelpers - Errors Saved at Wednesday, March 4, 2026 7:26:49 PM

LineNumber | LineString |ErrorDescription

97|96,VendorA,"Bad,Name",23.00,20|SupplierUpdate|In the field 'supplierPrice': Error Converting 'Name"' to type: 'Decimal'.

98|97,VendorB,Too,Many,Fields,34.00,22,EXTRA|SupplierUpdate|In the field 'supplierPrice': Error Converting 'Many' to type: 'Decimal'.

99|98,VendorC,MissingPrice,,25|SupplierUpdate|Line: 99 Column: 25. No value found for the value type field: 'supplierPrice' Class: 'SupplierUpdate'.  -> You must use the &#91;FieldNullValue] attribute because this is a value type and can't be null or use a Nullable Type instead of the current type.

100|99,VendorA,MissingField,44.00|SupplierUpdate|Line: 100 Column: 24. Delimiter ',' not found after field 'supplierPrice' (the record has less fields, the delimiter is wrong or the next field must be marked as optional).

 And, as you might expect, there’s support to read and parse the error file, as well, or to examine the errors at runtime.  In fact, setting a breakpoint after the read, we’ll see that between the engine and the result variables, we have all the information we need to ensure that all the records in the batch remain accounted-for.

Debug.WriteLine($"Read {engine.TotalRecords} records; {result.Length} successfully read, and {engine.ErrorManager.ErrorCount} errors");

Read 100 records; 96 successfully read, and 4 errors</code></pre>



<p>This ultra-simple example illustrates some elementary, but often-overlooked principles of batch record processing.</p>



<h3 class="wp-block-heading">Rule 1: Don’t lose records!</h3>



<p>As shown here, always know how many records you take as inputs, and when you’re done, you must always be able to account for each record - be they successfully processed or not.</p>



<h3 class="wp-block-heading">Rule 2: Explain what’s gone wrong.</h3>



<p>One of the things that’s made me a fan of filehelpers is the information provided when errors occur.&nbsp; Don’t make your users settle for anything less than knowing which records failed, and what’s gone wrong when failures occur.&nbsp; Note how, in the error file above, we can see line numbers, the original input line, and a clear description of why the record could not be read.&nbsp; These bits of information will make it quick and easy to find the offending record in the input file.&nbsp; Of course, you don’t have to use filehelpers to do this, and the presentation doesn’t need to look the same, but please do not leave your users wondering what went wrong or where to find the problem.</p>



<h2 class="wp-block-heading">Extending the sample</h2>



<p>So far, our example is still trivially simple.&nbsp; Let’s add another processing step – one more place for something to go wrong.&nbsp; The point of this made-up scenario was to load a supplier update file and update inventory numbers, so now that we’re able to read the supplier file, let’s try to update the inventory values in our made-up master database.</p>



<p>A little refactoring supports creation of a Sqlite context, loaded at runtime from the values we asked copilot to generate earlier:</p>



<pre class="wp-block-code"><code>    this.Database.EnsureDeleted();
    this.Database.EnsureCreated();
    string sql = File.ReadAllText(filename);
    this.Database.ExecuteSqlRaw(sql);</code></pre>



<p>I moved the filehelpers methods into a InventoryFileEngine class and created an IInventoryFileEngine interface to support testing.&nbsp; A new InventoryUpdate class supports injection of the db context and file engine, and I extracted a results class to hold status from one operation to the next:</p>



<pre class="wp-block-code"><code>    struct BatchFileResult
    {
        public string DataFilePath { get; set; }
        public string ErrorFilePath { get; set; }
        public int TotalRecords { get; set; }
        public int RecordReadSuccessCount { get; set; }
        public int RecordReadErrorCount { get; set; }
        public int RecordUpdateSuccessCount { get; set; }
        public int RecordUpdateErrorCount { get; set; }
    }</code></pre>



<p>With these changes in place, we can illustrate an update step that matches the part against our “master” data so that we could (presumably) update the inventory level for each part.  This lookup is trivially simple, and of course, we’d be very unlikely to look up the part based on part name, but I chose to work with the sample data generated by copilot, and I don’t think this diminishes the point of the sample.</p>



<pre class="wp-block-code"><code>        public BatchFileResult ApplyUpdates()
        {
            var result = _engine.Result;

            int successCount = 0;

            int updateFailureCount = 0;
            if (_engine.SupplierUpdateRecs != null)
            {
                foreach (var record in _engine.SupplierUpdateRecs)
                {
                    var partName = record.partName;
                    if (!string.IsNullOrEmpty(partName))
                    {
                        var foundPart = _context.FindPartByPartName(partName);
                        if (foundPart != null)
                        {
                            successCount++;
                        }
                        else
                        {
                            updateFailureCount++;
                        }
                    }
                }
                result.RecordUpdateSuccessCount = successCount;
                result.RecordUpdateFailureCount = updateFailureCount;
            }
            return result;
        }</code></pre>



<p>The result structure now has enough information to track input records through both initial read and downstream update operations, and armed with this sort of data, we can create a sankey diagram in mermaid to show these results graphically.  Here’s the mermaid syntax:<br></p>



<pre class="wp-block-code"><code>config:
  sankey:
    showValues: true
---
sankey-beta

Read, Parsed, 96
Read, Parse-Error, 4
Parsed, Update Success, 80
Parsed, Update Failure, 16</code></pre>



<p></p>



<p><br>And the resulting graph:</p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="525" height="355" data-attachment-id="2620" data-permalink="http://blog.componentoriented.com/2026/03/accounting-for-batch-operations/image-9/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png?fit=600%2C406" data-orig-size="600,406" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png?fit=300%2C203" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png?fit=525%2C355" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png?resize=525%2C355" alt="" class="wp-image-2620" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png?w=600 600w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/03/image.png?resize=300%2C203 300w" sizes="(max-width: 525px) 100vw, 525px" /></a></figure>



<p>I love mermaid for applications like this because the syntax requires no graphics processing at all.&nbsp; The resulting syntax drops right into wikis (including github, Confluence, and AZDO), or if you want to build them into an application, you can include the mermaid rendering JS in your app.</p>



<h2 class="wp-block-heading">Advanced Considerations</h2>



<p>Armed with a multi-step batch file processing example, as well as the ability to visualize results of processing, you can consider how you’d expect to handle problems when they arise.&nbsp; In this fictional example, for instance, you’d want to understand whether it’s acceptable to process updates for just the parts considered successful here.&nbsp; There may be cases where the entire batch needs to succeed or fail together.&nbsp; In these cases, you’d need to reject a file like this as soon as you see any bad records at all so that a replacement file can be re-submitted.&nbsp;&nbsp;</p>



<p>Options to reject and retry may be limited by the size of batches.&nbsp; In some cases, pre-processing a batch of hundreds of thousands or millions of rows just to reject the batch is impractical and non-performant.&nbsp; In these cases, an ability to output bad rows as a separate file (as shown earlier) can allow those records to be repaired and re-submitted.&nbsp;&nbsp;</p>



<p>The techniques shown here can and should be modified, combined, and extended for specific applications.&nbsp; The root principles (don’t lose records, and explain yourself) should remain, though.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://blog.componentoriented.com/2026/03/accounting-for-batch-operations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2619</post-id>	</item>
		<item>
		<title>Exploring Github Copilot Instructions</title>
		<link>http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/</link>
					<comments>http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/#respond</comments>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Wed, 11 Feb 2026 03:15:10 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2600</guid>

					<description><![CDATA[A year ago, we were just getting comfortable with prompt engineering.&#160; Vibe coding wasn’t yet common parlance.&#160; A lot changes in a year.&#160; Today, Copilot is an ever-present companion for many of us, and getting the most out of it is an ever-evolving part of the practice of development. Just as effective prompt engineering makes &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/" class="more-link">Continue reading<span class="screen-reader-text"> "Exploring Github Copilot Instructions"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>A year ago, we were just getting comfortable with prompt engineering.&nbsp; Vibe coding wasn’t yet common parlance.&nbsp; A lot changes in a year.&nbsp; Today, Copilot is an ever-present companion for many of us, and getting the most out of it is an ever-evolving part of the practice of development.</p>



<p>Just as effective prompt engineering makes a big difference in the quality of output you can get from an LLM, the right guidance for Copilot will help it produce better work - especially in agent mode.&nbsp; This article will focus on creation and use of Copilot-instructions to guide operation on a repo-by-repo basis, but before diving in, let’s review a few options in this ever-evolving area.</p>



<h2 class="wp-block-heading">Instructing Copilot</h2>



<p>The principle difference between prompt engineering and Copilot instructions is that prompt engineering tends to be transient and is certainly localized to your account-specific context.&nbsp;</p>



<p>It’s true that <a href="https://techcommunity.microsoft.com/blog/microsoft365copilotblog/introducing-copilot-memory-a-more-productive-and-personalized-ai-for-the-way-you/4432059">Copilot Memory</a> is attempting to fill this gap, and given emerging tools like <a href="https://openclaw.ai/">OpenClaw</a>, the transient nature of prompt engineering seems sure to disappear, but instructions that stick to you individually won’t maintain consistency in a team setting.</p>



<p>Enter Copilot-instructions, which let you save your project-wide Copilot instructions in a file that lives with your repo, visible to anyone who pulls your repo.&nbsp; We’ll explore this option in depth shortly, but keep in mind this is not the only option to capture design and coding guidance for AI coding tools.</p>



<p>For guidance that can extend across multiple repositories, consider MCP integration with your project organization tool of choice.&nbsp; Atlassian offers an <a href="https://www.atlassian.com/blog/announcements/remote-mcp-server">MCP server</a> for its cloud-hosted Jira and Confluence, and Microsoft has a similar <a href="https://medium.com/@abhinav.shastri/boost-productivity-connect-github-copilot-azure-devops-via-mcp-server-fe08c0f9bc73">MCP server for AZDO</a>, as well as a <a href="https://learn.microsoft.com/en-us/microsoftsearch/azure-devops-wiki-connector">Copilot connector</a>.&nbsp; Of these tools, the only one I’ve tried personally is the Atlassian MCP server, which operates mostly as-advertised.&nbsp; Note that when used properly, the MCP integrations above can guide programming standards across all your code bases all at once, which is the stuff of dreams for enterprise architects - as long as these standards can be expressed efficiently and effectively.&nbsp; It’s also worth pointing out that all these techniques can coexist, with Copilot-instructions operating in a bootstrapping manner to access enterprise-wide standards.</p>



<h2 class="wp-block-heading">Getting Started</h2>



<p>The basics for Copilot-instructions are as simple as could be - simply drop a file called <a href="http://copilot-instructions.md">Copilot-instructions.md</a> in a .github folder in your repo.&nbsp; <a href="https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions">Docs from github</a> will go into detail on options like instructions that apply only to some paths or some agent LLMs.&nbsp; This page also offers a prompt you can use to generate a Copilot-instructions file based on your existing repo, and that’s what we’ll try here.</p>



<p>For this example, I’m using a simple solution I created to explore vertical-slice architecture in dotnet Aspire.&nbsp; You can find the “before” version of this repo here:&nbsp; <a href="https://github.com/dlambert-personal/AspireTodo">https://github.com/dlambert-personal/AspireTodo</a></p>



<p>For this demo, I’m going to use <a href="https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions#asking-copilot-coding-agent-to-generate-a-copilot-instructionsmd-file">the prompt</a> shown in the github docs verbatim.&nbsp; I do believe that were I doing this in earnest, I’d want to customize the prompt somewhat.&nbsp; I may also want to explore how this prompt works against repos of varying size, maturity, and cohesiveness. For instance, the bit where we ask Copilot to derive architectural elements of the project is especially interesting.&nbsp; In a trivial example like this, I expect Copilot to find those elements quite effectively, but in a larger repo – one with some development history – I wouldn’t be surprised to see the model struggle a bit.&nbsp; For reference, I’m using the Claude-Haiku 4.5 model in agent mode.</p>



<h2 class="wp-block-heading">Reviewing the Results</h2>



<p>So, how did the prompt do in its out-of-the box form?&nbsp; Really not too bad; there’s some great content here.&nbsp; My chief criticism (and one that’s easily corrected) is that I believe much of the generated content would do well migrated to the <a href="http://readme.md">readme.md</a> file, which I’ll do when I commit the “after” results to a new repo.&nbsp; So, section-by-section, here’s what was produced.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<ul class="wp-block-list">
<li>Repository Overview.&nbsp; A good summation of the solution - probably better-suited to live in the readme.&nbsp; All the information cited here is accurate and useful.</li>



<li>Project Structure.&nbsp; Definitely useful, and definitely a section I’d look to move to readme. In fact, I believe I’d replace this in the instructions file with an explicit instruction to keep this area current as the project evolves.&nbsp; Along these lines, I’d like to see how Copilot handles the project as it grows in size. The level of detail shown in this section could grow unwieldy in a larger solution.</li>



<li>Build &amp; Development Workflow.&nbsp; I found this section interesting.&nbsp; It’s the first place I spotted an outright mistake (indicating that VS2022 would be able to work with .Net 10).&nbsp; Beyond that, the instructions seem thorough and useful, including some startup alternatives that could prove helpful and database setup and migration instructions as well.&nbsp; Once again, it’s possible much of this would be well-suited to live in the readme file.</li>



<li>Architecture and Key Patterns.&nbsp; An excellent section that summarizes design patterns and key integrations.&nbsp; Again, frankly, one could argue this material could live in the readme.</li>
</ul>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg"><img data-recalc-dims="1" decoding="async" width="525" height="195" data-attachment-id="2607" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/generated1/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?fit=1600%2C594" data-orig-size="1600,594" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="generated1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?fit=300%2C111" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?fit=525%2C195" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?resize=525%2C195" alt="" class="wp-image-2607" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?resize=1024%2C380 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?resize=300%2C111 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?resize=768%2C285 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?resize=1536%2C570 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated1.jpg?w=1600 1600w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p></p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg"><img data-recalc-dims="1" decoding="async" width="525" height="165" data-attachment-id="2608" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/generated2/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?fit=1660%2C520" data-orig-size="1660,520" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="generated2" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?fit=300%2C94" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?fit=525%2C165" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?resize=525%2C165" alt="" class="wp-image-2608" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?resize=1024%2C321 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?resize=300%2C94 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?resize=768%2C241 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?resize=1536%2C481 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated2.jpg?w=1660 1660w" sizes="(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p></p>



<p></p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="204" data-attachment-id="2609" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/generated3/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?fit=1508%2C586" data-orig-size="1508,586" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="generated3" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?fit=300%2C117" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?fit=525%2C204" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?resize=525%2C204" alt="" class="wp-image-2609" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?resize=1024%2C398 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?resize=300%2C117 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?resize=768%2C298 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated3.jpg?w=1508 1508w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p></p>



<p></p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="185" data-attachment-id="2610" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/generated4/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?fit=1554%2C548" data-orig-size="1554,548" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="generated4" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?fit=300%2C106" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?fit=525%2C185" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?resize=525%2C185" alt="" class="wp-image-2610" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?resize=1024%2C361 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?resize=300%2C106 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?resize=768%2C271 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?resize=1536%2C542 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated4.jpg?w=1554 1554w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>
</div>
</div>



<ul class="wp-block-list">
<li>Common Tasks &amp; Gotchas.&nbsp; This section began to get a bit odd.&nbsp; There’s a bit on adding a new API endpoint that’s pretty good, but there’s also a bit on debugging JSON deserialization that seems like pretty general debugging techniques I’d expect to uncover when needed in Ask mode.</li>



<li>Known Issues &amp; Workarounds.&nbsp; This entire section suffers from the same problem - this entire section should be discoverable when needed and debugging topics shouldn’t be limited to the few topics presented in this section.</li>
</ul>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<ul class="wp-block-list">
<li>Code Style &amp; Conventions.&nbsp; A short, but useful section - definitely one I’d keep in instructions and expand upon.&nbsp; In my opinion, this is exactly the sort of guidance I’d want to see in Copilot-instructions.&nbsp; Expanding on this area could help keep coding style consistent as the codebase evolves - one of the main objectives of Copilot-instructions in the first place.&nbsp; If you begin orchestrating instructions across repos (using MCP connections to a wiki, for instance), conventions like this would be appropriate for enterprise-wide use.</li>



<li>Troubleshooting Build Failures.&nbsp; Back to material that should be general knowledge.</li>



<li>Final Notes.&nbsp; Another useful section I’d keep and expand upon.&nbsp; The are specific instructions to the Copilot agent - again, exactly the instructions I’d want to see in this file.</li>
</ul>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="168" data-attachment-id="2611" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/generated5/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?fit=1579%2C504" data-orig-size="1579,504" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="generated5" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?fit=300%2C96" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?fit=525%2C168" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?resize=525%2C168" alt="" class="wp-image-2611" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?resize=1024%2C327 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?resize=300%2C96 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?resize=768%2C245 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?resize=1536%2C490 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/generated5.jpg?w=1579 1579w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>
</div>
</div>



<h2 class="wp-block-heading">Putting it to the test</h2>



<p>Before I fine-tune the instructions, I wanted to test Copilot to see if it seemed any smarter with the addition of the initial draft.&nbsp; I made a copy of the original repo (so you can see the “before” version) and asked Copilot (still using Claude 4.5) the following:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="has-text-color has-link-color wp-elements-e34d010521ac9fb7b5f0d0a780185dd8" style="color:#000faf"><em>This solution needs to be refactored to account for a name change.&nbsp; Replace all references to AspireTodo with AspireTickets.&nbsp; Include solution name, project names, namespaces, component files, and variable names.&nbsp; As always, the solution should build successfully when you're done.</em></p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>



<p>Copilot plotted a six-step plan and got stuck on step 2:</p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="has-text-color has-link-color wp-elements-72ffaf880334c190a0dfb3de7199cc2d" style="color:#006e00">This refactoring is extensive. Let me take a systematic approach:</p>



<p class="has-text-color has-link-color wp-elements-147b8edcc82f32e5ac43a1258857990a" style="color:#006e00">1.&nbsp; &nbsp; First, I'll gather all the source files that need namespace updates<br>2.   Rename directories and projects<br>3.&nbsp; &nbsp;Update all namespace declarations<br>4.&nbsp; &nbsp; Update project references<br>5.&nbsp; &nbsp; Verify the build succeeds</p>
</blockquote>


<div class="wp-block-image">
<figure class="alignright size-full is-resized"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="516" height="429" data-attachment-id="2602" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/image-6/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png?fit=516%2C429" data-orig-size="516,429" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png?fit=300%2C249" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png?fit=516%2C429" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png?resize=516%2C429" alt="" class="wp-image-2602" style="width:349px;height:auto" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png?w=516 516w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-1.png?resize=300%2C249 300w" sizes="auto, (max-width: 516px) 100vw, 516px" /></a></figure>
</div>


<p>This is a problem I’ve seen frequently with Copilot: the powershell integration has a tendency to fail or freeze.&nbsp; After closing the terminal, Copilot realized it needed another approach, and it tried again.&nbsp; This went on for a while - the whole time showing success sprinkled with failure.&nbsp; Had this been an actual developer, I’d have stopped them and suggested they revisit their plan, as it sure looks like a lot of guessing going on here.&nbsp; The “stuck in powershell” problem continued as well – it seemed like Copilot expected powershell to understand commands that powershell just whiffed on - despite my installation being reasonably current.&nbsp;&nbsp;</p>


<div class="wp-block-image">
<figure class="alignright size-full is-resized"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="510" height="244" data-attachment-id="2603" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/image-7/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png?fit=510%2C244" data-orig-size="510,244" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png?fit=300%2C144" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png?fit=510%2C244" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png?resize=510%2C244" alt="" class="wp-image-2603" style="aspect-ratio:2.090101212117471;width:306px;height:auto" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png?w=510 510w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-2.png?resize=300%2C144 300w" sizes="auto, (max-width: 510px) 100vw, 510px" /></a></figure>
</div>


<p>Finally, adding insult to injury, Copilot eventually just stopped working new tasks - apparently before it was done.&nbsp; I gave it a little nudge, and sure enough - there was a little more work to do.</p>



<p>Finally - the build.&nbsp; First attempt: five errors and two warnings.&nbsp; All five errors were project files not found.&nbsp; Copilot blamed them on Visual Studio’s cache, which seemed like a class-one cop-out.&nbsp;</p>


<div class="wp-block-image">
<figure class="alignright size-large is-resized"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="102" data-attachment-id="2612" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/build1/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?fit=1461%2C283" data-orig-size="1461,283" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="build1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?fit=300%2C58" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?fit=525%2C102" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?resize=525%2C102" alt="" class="wp-image-2612" style="aspect-ratio:5.171600878277854;width:314px;height:auto" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?resize=1024%2C198 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?resize=300%2C58 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?resize=768%2C149 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/build1.jpg?w=1461 1461w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>
</div>


<p>More confusion… more freezes.&nbsp; Eventually, Copilot froze hard enough I couldn’t even cancel in the chat window, so I took matters into my own hands.&nbsp; It turns out that aside from the file renaming issues, Copilot had gotten pretty close.&nbsp; I had one “using” statement I needed to patch, and all the project files had to be removed and re-added, but once that was complete, I was back to building w/o errors again.</p>



<p>When all is said and done, did Copilot save me time in this refactor?&nbsp; Maybe - but not by a ton.&nbsp; I think the level of supervision is still quite high, but the remaining rough bits seem like they’re not so much a model shortcoming as an integration snafu - hopefully an easy update.</p>



<h2 class="wp-block-heading">Next Challenge - adding to the UI</h2>



<p>I suspected Copilot might do better with a change that didn’t require so many file manipulations, so I gave it a simple prompt that would require touching the solution in several places:</p>



<p class="has-text-color has-link-color wp-elements-d9face4792cb08e00095d02f538596d1" style="color:#000faf"><br><em>Modify the current page to be able add tickets in addition to displaying them.&nbsp; Use the CreateTicket command in the API project to save the new ticket.</em></p>



<p>This request was dispatched quickly, and seems to have produced a high-quality output:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="255" data-attachment-id="2604" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/image-8/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?fit=2048%2C993" data-orig-size="2048,993" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?fit=300%2C145" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?fit=525%2C255" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?resize=525%2C255" alt="" class="wp-image-2604" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?resize=1024%2C497 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?resize=300%2C145 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?resize=768%2C372 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?resize=1536%2C745 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image-3.png?w=2048 2048w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>The project still built, and when I ran it, I saw a plausible-looking form.  The bad news: the form wasn’t submitting anything to the API, so I asked Copilot to fix it.  The resulting back &amp; forth was almost comedic. I wasn’t about to let Copilot off the hook, and it did eventually figure it out.  I’d begun counting form submission attempts, and (if my count was right), it took well over two dozen stabs at this to get the form to submit successfully:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="319" data-attachment-id="2616" data-permalink="http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/form/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?fit=1808%2C1099" data-orig-size="1808,1099" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="form" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?fit=300%2C182" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?fit=525%2C319" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?resize=525%2C319" alt="" class="wp-image-2616" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?resize=1024%2C622 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?resize=300%2C182 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?resize=768%2C467 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?resize=1536%2C934 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/form.jpg?w=1808 1808w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p><br>It could be argued that very little architectural guidance was provided to Copilot in the instructions we’d created, so the Blazor form submission stuff it struggled with might almost have been excusable.  I’m still a little disappointed here, though, because the real gotcha seems to have been an addition to .NET 10 (@rendermode) that was keeping the form from operating interactively, and the .NET version absolutely was covered in copilot-instructions.</p>



<p>For the last test / evaluation, I’m going to switch from Visual Studio 2026 to VS Code, and I’ll modify my prompt to specifically call out use of copilot-instructions, though I don’t believe this should be necessary.&nbsp; This is the prompt used (including a typo on “architectural”):</p>



<p class="has-text-color has-link-color wp-elements-8260fe8fb449ea0f994d7c31aaa6ce4c" style="color:#000faf">Modify the solution to be able edit tickets that have been previously saved.&nbsp; Use copilot-instructions.md as a reference to architecural norms in this solution.&nbsp; Include UI changes to the Tickets page to load a ticket to the form for editing and submit changes to the API.&nbsp; The API project will need a new EditTicket command to save the ticket.</p>



<p>I’ll be honest - I wasn’t expecting a drastic change in experience going from Visual Studio to VS Code, but wow, what a difference there was!&nbsp; The initial change was quick, and as near as I can tell, perfectly functional.&nbsp; I got an updated form that works as expected, the API adapter works, the API has been extended to include a new command, and the new command follows the design conventions in use by previous commands.&nbsp; VS Code even asked me if I wanted it to test the new functionality, and that went slightly less-well – the spots where Copilot integrates to the world around it really do seem to be its Achilles heel right now.</p>



<p>Up to this last step, I believe I’d have given Copilot a barely-passing grade.&nbsp; A co-worker from years back always used to remind me we don’t cheer for the dancing bear because it dances <em>well</em> – we cheer because bears aren’t supposed to dance at all, and this is about how I’d have summed up my experience with Copilot in Visual Studio.&nbsp; Copilot in VS Code, on the other hand, seems much more well-sorted.</p>



<p>I’m still impressed with the amount of project documentation Copilot was able to sift out of my project, and I intend to press on with pulling some of the docs into readme and fine-tuning the bits that are left to be more prescriptive – in short, leaning on this a little more to get a feel for the edge cases.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://blog.componentoriented.com/2026/02/exploring-github-copilot-instructions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2600</post-id>	</item>
		<item>
		<title>Creating expectations in UX</title>
		<link>http://blog.componentoriented.com/2026/02/creating-expectations-in-ux/</link>
					<comments>http://blog.componentoriented.com/2026/02/creating-expectations-in-ux/#respond</comments>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Tue, 03 Feb 2026 02:15:24 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[usability]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2595</guid>

					<description><![CDATA[I’ve written previously about the effectiveness and extensibility of Expected and Actual as a mental model to understand usability.&#160; As a reminder, when the actual experience we offer users matches the expected experience they bring to the interaction, the user experiences no friction, and hence, usability is considered good. In most cases, we accept “expected” &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2026/02/creating-expectations-in-ux/" class="more-link">Continue reading<span class="screen-reader-text"> "Creating expectations in UX"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>I’ve written previously about the effectiveness and extensibility of <a href="https://medium.com/@davidglambert/expected-and-actual-92d02085ead2?source=user_profile_page---------4-------------4fd621d5bb3b----------------------">Expected and Actual</a> as a mental model to understand usability.&nbsp; As a reminder, when the actual experience we offer users matches the expected experience they bring to the interaction, the user experiences no friction, and hence, usability is considered good.</p>



<p>In most cases, we accept “expected” where we find it and do our best to match it with “actual”.&nbsp; This is an important part of usability writings like Don Noman’s “Design of Everyday Things” and Steve Krug’s “Don’t Make Me Think”, which help us understand how users have formed their expectations and how we can map software systems to those expectations.</p>



<p>I recently experienced an update from LinkedIn, however, that helped me appreciate how “expected” can be influenced.&nbsp; On its surface, this update resembles others you may have seen - an email, perhaps, or an SMS you opted into a couple years ago, but this one struck me as just the right amount of notification in just the right place.</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="153" data-attachment-id="2596" data-permalink="http://blog.componentoriented.com/2026/02/creating-expectations-in-ux/image-5/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?fit=1080%2C315" data-orig-size="1080,315" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?fit=300%2C88" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?fit=525%2C153" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?resize=525%2C153" alt="" class="wp-image-2596" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?resize=1024%2C299 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?resize=300%2C88 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?resize=768%2C224 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2026/02/image.png?w=1080 1080w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>An important part of making this work is the channel in which this update is presented.&nbsp; This update displayed right in the normal LinkedIn updates feed, which made it likely I’d see it, and also made it feel less spammy than a separate notification out of the blue.</p>



<p>It’s also possible that I appreciated this notification more because not a week prior to this, I had a UX experience that felt very different.&nbsp; My experience began with an alert from my bank.&nbsp; Had I just authorized a payment of [x] from Amazon?&nbsp; I didn’t recognize the amount, and checked my Amazon account to see if this corresponded to any recent orders.&nbsp; It didn’t, so I answered as such to my bank, and they immediately paused my card.&nbsp; It was late, and I didn’t feel like finding out if my bank had extended hours support, so I decided to deal with it in the morning.&nbsp; Of course, I forgot about it until lunch, when I reached for my card and remembered it wasn’t going to work.&nbsp; A call to the bank was next, and the first thing they asked was whether I was a prime member, and might this be a renewal charge.&nbsp; Sure enough - that was it.&nbsp; Still no idea why it triggered the bank’s fraud algorithm, but the overall experience from soup to nuts felt crude and disrespectful - especially by comparison to the LinkedIn experience I saw later.</p>



<p>As always, the lesson here is best-applied broadly.&nbsp; If you have an opportunity to form expectations anywhere in your UX, you’re far more likely to be able to meet them, and met expectations are gold in UX.</p>
]]></content:encoded>
					
					<wfw:commentRss>http://blog.componentoriented.com/2026/02/creating-expectations-in-ux/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2595</post-id>	</item>
		<item>
		<title>Viral adoption, or just creepy?</title>
		<link>http://blog.componentoriented.com/2025/04/viral-adoption-or-just-creepy/</link>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Thu, 03 Apr 2025 12:25:01 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[The Business of Software]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[uncomfortable]]></category>
		<category><![CDATA[usability]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2589</guid>

					<description><![CDATA[Earlier this week, I had a very short, very disconcerting Teams meeting.  Shortly after the meeting began, I and the other participant noticed an AI bot show up.  Neither of us were aware we’d invited it, which as you can imagine, was puzzling and more than a little disconcerting to both of us.  Believing discretion &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2025/04/viral-adoption-or-just-creepy/" class="more-link">Continue reading<span class="screen-reader-text"> "Viral adoption, or just creepy?"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>Earlier this week, I had a very short, very disconcerting Teams meeting.  Shortly after the meeting began, I and the other participant noticed an AI bot show up.  Neither of us were aware we’d invited it, which as you can imagine, was puzzling and more than a little disconcerting to both of us.  Believing discretion is the better part of valor, we both agreed to abandon the meeting, and I did some googling.</p>



<p>It turns out it was my fault (ultimately) the bot showed up in the meeting.&nbsp; Last week, I’d attended a meeting where one of the hosts had invited this bot to take notes. &nbsp; Following the meeting, the bot sent a meeting summary to all the attendees (so far, so good).&nbsp; Having missed a specific point I wanted to look up, I tried accessing the meeting transcript, and was prompted to create an account.</p>



<p>What I hadn’t seen was the (in my opinion) extremely aggressive default behavior on the part of this platform to just go ahead and invite itself to any and all of my future meetings – even those in which I was a guest!</p>



<p>In hindsight, there are at least a couple of lessons to be learned from this experience.&nbsp; First, as a consumer, all those damned EULAs actually matter.&nbsp; Choose your connections with care, and consider the permissions you bestow on apps.&nbsp; It may be a bit inconvenient to pass on an app that seems a bit greedy, but every once in a while, it just may save your bacon.</p>



<p>The other lesson is offered from the perspective of a product designer, and it requires a bit of empathy with respect to your user / audience.&nbsp; Please consider - realistically - the behaviors your users might actually expect of your product.&nbsp; In the case of this meeting note-taker, it’s appropriate to have the bot offer its services when I create meetings.&nbsp; Ideally, I’d have liked to see something interactive the first time the bot has the opportunity to hop on a meeting, and if I elect, “yes”, I think it would be appropriate to ask if I want that to be the behavior for future meetings.&nbsp; Offer a service to me, but don’t surprise me by joining behind my back!&nbsp;&nbsp;</p>



<p>I also believe there should have been distinct behaviors for meetings I host vs. meetings I join as a guest.&nbsp; In my opinion, it would be appropriate for the bot to ask to attend <em>any</em> meeting I join as a guest.&nbsp; Finally, when joining, the behavior I saw in the meeting was very poorly-sequenced.&nbsp; When the bot joined my meeting unexpectedly, it hung around for a good 30 seconds before offering anything in the chat window – a definite faux-pas.&nbsp; I’d expect to <em>immediately</em> see a message indicating who invited the bot and offering actions to stop the recording if desired.&nbsp; Having this message show up half-a-minute into the meeting is no bueno.</p>



<p>The era of AI is upon us.&nbsp; Platforms can now do more for us than ever before, and what we’ve seen so far is just the beginning.&nbsp; However, usability / UX practices need to be engaged for this journey so that these capabilities can be seen as helpful and not subversive or destructive.&nbsp; Help you expect will always be welcome, but please beware helping in ways that aren’t expected - at best, this will be interpreted as “handling”, and at worst, it’s an unwelcome intrusion.&nbsp; The first interactions you have with a new customer are precious - don’t botch them and risk losing a customer forever by overstepping your welcome!</p>



<p></p>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2589</post-id>	</item>
		<item>
		<title>Simulating CDC with dbt snapshots</title>
		<link>http://blog.componentoriented.com/2025/01/simulating-cdc-with-dbt-snapshots/</link>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Thu, 23 Jan 2025 19:20:54 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[CDC]]></category>
		<category><![CDATA[data]]></category>
		<category><![CDATA[dbt]]></category>
		<category><![CDATA[warehouse]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2581</guid>

					<description><![CDATA[In data modeling, it's common to encounter modeling problems where it's not sufficient to know the data values of an entity right now. You need to be able to know what the values were at a previous point in time. This is most-commonly a data analytics problem, which means this type of model won't be &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2025/01/simulating-cdc-with-dbt-snapshots/" class="more-link">Continue reading<span class="screen-reader-text"> "Simulating CDC with dbt snapshots"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>In data modeling, it's common to encounter modeling problems where it's not sufficient to know the data values of an entity <em>right now</em>.  You need to be able to know what the values were at a previous point in time. This is most-commonly a data analytics problem, which means this type of model won't be present in native domain tables.</p>



<h2 class="wp-block-heading">Data changes over time</h2>



<p>Let's break down the problem.  I'm going to use tables / scenarios from Microsoft's ubiquitous Contoso model.  This is fairly well-known, quite generic, and easy to obtain.  In fact, if you're interested in downloading data sets in various sizes for these tables (and more), they're<a href="https://github.com/sql-bi/Contoso-Data-Generator-V2-Data/releases/tag/ready-to-use-data" data-type="link" data-id="https://github.com/sql-bi/Contoso-Data-Generator-V2-Data/releases/tag/ready-to-use-data"> available here</a>, and a tool that lets you generate sample data of your own can be found <a href="https://www.sqlbi.com/tools/contoso-data-generator/">here</a> (might be worth checking this out for generating test data for your own schema, too).</p>



<p>I'm going to focus on some hypothetical changes to the Product table.  The Contoso tables are fairly well-engineered for "demo" applications, and the Product table is no different.  This is a good baseline for what you'd expect to see in a table like this in a real application.  The columns are pretty straightforward, and in this exercise, I'm going to focus on changes that might occur in cost or price over time.  Some of these columns are really unlikely to change without resulting in a new sku.</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th class="has-text-align-left" data-align="left"><strong>Column Name</strong></th><th>Data type </th><th>Notes</th></tr></thead><tbody><tr><td class="has-text-align-left" data-align="left">productkey </td><td>int4 </td><td>synthetic key</td></tr><tr><td class="has-text-align-left" data-align="left">productcode </td><td>int4 </td><td>natural key</td></tr><tr><td class="has-text-align-left" data-align="left">productname </td><td>text </td><td></td></tr><tr><td class="has-text-align-left" data-align="left">manufacturer </td><td>text </td><td></td></tr><tr><td class="has-text-align-left" data-align="left">brand </td><td>text </td><td></td></tr><tr><td class="has-text-align-left" data-align="left">color </td><td>text</td><td></td></tr><tr><td class="has-text-align-left" data-align="left">weightunit </td><td>text</td><td></td></tr><tr><td class="has-text-align-left" data-align="left">weight </td><td>float8</td><td></td></tr><tr><td class="has-text-align-left" data-align="left">cost </td><td>float8 </td><td>Likely to change over time</td></tr><tr><td class="has-text-align-left" data-align="left">price </td><td>float8 </td><td>Likely to change over time</td></tr><tr><td class="has-text-align-left" data-align="left">categorykey </td><td>int4 </td><td></td></tr><tr><td class="has-text-align-left" data-align="left">categoryname </td><td>text </td><td></td></tr><tr><td class="has-text-align-left" data-align="left">subcategorykey </td><td>int4 </td><td></td></tr><tr><td class="has-text-align-left" data-align="left">subcategoryname </td><td>text </td><td></td></tr></tbody></table></figure>



<p>In our imaginary data warehouse scenario, we'd like to be able to see changes in columns like cost, price, category, and so on over time.  Perhaps we're interested in historic performance of products from different manufacturers, or fill in your own hypothetical, and since these changes over time aren't already present in the source data, we're going to show how to generate these rows using dbt. </p>



<p>In data warehousing, the visibility of changes like this over time is known as a <a href="https://en.wikipedia.org/wiki/Slowly_changing_dimension">slowly-changing-dimension</a> (SCD), and there are a number of ways to model them.  In this example, we're going to show type-2, which creates a new row when changes are detected.   In order to do this sort of change detection, you'd need to be able to tell when a row has changed, implying the need for a row ID.  Then, each field in the row would have to be compared to see if any of them have changed.  Not sophisticated, but unwieldy.  Luckily, dbt can do this for us with some simple declarative syntax. </p>



<h2 class="wp-block-heading">dbt Snapshots</h2>



<p>If you're building a data warehouse, there's a good chance you're already using dbt.  This open-source tool is available in host-yourself form (dbt core) or as a SAS platform (dbt cloud).  The example shown here is built for dbt core, and is <a href="https://github.com/dlambert-personal/CDC-with-dbt-demo">available on github</a>.  This example uses a dbt seed to load the Contoso Product table - not the way you'd do this at scale, but it makes the demo more portable.  Another nod to demo portability - we're using Postgres as our sample data warehouse.  All the tools here are fairly agnostic with respect to database, so these techniques adapts to your tool of choice.</p>



<p>The dbt syntax to compute the type-2 SCD is called a snapshot.  In our example, we name the output table (producthistory) and the source to be monitored (product).  In the config section, note the strategy, which can be either "timestamp" if your source data has a last-updated-date column, or 'check', which evaluates values of columns.   Also note the "check_cols" - the value of "all" here is quick and easy, but typically a list of specific columns is preferred for the same reasons we don't use "select *".</p>



<pre class="wp-block-code"><code>snapshots:
  - name: producthistory
    relation: ref('CDC_with_dbt', 'product')
    description:  Slow-changing dimension view of product
    config:
      database: cdcdemo
      strategy: check
      unique_key: ProductKey
      check_cols: all
      hard_deletes: invalidate </code></pre>



<p>Let's take a look at these snapshots in action.  We're going to make changes to the following two rows (shown here in JSON to list fields as rows), and the highlighted columns:</p>



<pre class="wp-block-code"><code>{
"select * from dbt.product where productcode in (0101001, 0101002) \r\n": &#91;
	{
		"productkey" : 1,
		"productcode" : 101001,
		"productname" : <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color"><strong>"Contoso 512MB MP3 Player E51 Silver"</strong></mark>,
		"manufacturer" : "Contoso, Ltd",
		"brand" : "Contoso",
		"color" : "Silver",
		"weightunit" : "ounces",
		"weight" : 4.8,
		"cost" : 6.62,
		"price" : <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color"><strong>12.99</strong></mark>,
		"categorykey" : 1,
		"categoryname" : "Audio",
		"subcategorykey" : 101,
		"subcategoryname" : "MP4&amp;MP3"
	},
	{
		"productkey" : 2,
		"productcode" : 101002,
		"productname" : <strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">"Contoso 512MB MP3 Player E51 Blue"</mark></strong>,
		"manufacturer" : "Contoso, Ltd",
		"brand" : "Contoso",
		"color" : "Blue",
		"weightunit" : "ounces",
		"weight" : 4.1,
		"cost" : 6.62,
		"price" : <strong><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-green-cyan-color">12.99</mark></strong>,
		"categorykey" : 1,
		"categoryname" : "Audio",
		"subcategorykey" : 101,
		"subcategoryname" : "MP4&amp;MP3"
	}
]}</code></pre>



<p>When we update these fields and run dbt against the updated data, the producthistory table will now show four rows, corresponding to the two original rows and the updated version of each respective row:</p>



<pre class="wp-block-code"><code>{
"select * from dbt.producthistory where productcode in (0101001, 0101002) \r\n": &#91;
	{
		"productkey" : 1,
		"productcode" : 101001,
		"productname" : "Contoso 512MB MP3 Player E51 Silver",
		"manufacturer" : "Contoso, Ltd",
		"brand" : "Contoso",
		"color" : "Silver",
		"weightunit" : "ounces",
		"weight" : 4.8,
		"cost" : 6.62,
		"price" : 12.99,
		"categorykey" : 1,
		"categoryname" : "Audio",
		"subcategorykey" : 101,
		"subcategoryname" : "MP4&amp;MP3",
		"dbt_scd_id" : "daef36db7500c9e531447a11e0e3107a",
		"dbt_updated_at" : "2025-01-17T00:35:19.923Z",
		"dbt_valid_from" : "2025-01-17T00:35:19.923Z",
		"dbt_valid_to" : "2025-01-17T00:37:05.581Z"
	},
	{
		"productkey" : 2,
		"productcode" : 101002,
		"productname" : "Contoso 512MB MP3 Player E51 Blue",
		"manufacturer" : "Contoso, Ltd",
		"brand" : "Contoso",
		"color" : "Blue",
		"weightunit" : "ounces",
		"weight" : 4.1,
		"cost" : 6.62,
		"price" : 12.99,
		"categorykey" : 1,
		"categoryname" : "Audio",
		"subcategorykey" : 101,
		"subcategoryname" : "MP4&amp;MP3",
		"dbt_scd_id" : "4b8654fbe0b5aad13f42ec52524dbe2b",
		"dbt_updated_at" : "2025-01-17T00:35:19.923Z",
		"dbt_valid_from" : "2025-01-17T00:35:19.923Z",
		"dbt_valid_to" : "2025-01-17T00:37:05.581Z"
	},
	{
		"productkey" : 1,
		"productcode" : 101001,
		"productname" : "Contoso 512MB MP3 Player E52 Silver",
		"manufacturer" : "Contoso, Ltd",
		"brand" : "Contoso",
		"color" : "Silver",
		"weightunit" : "ounces",
		"weight" : 4.8,
		"cost" : 6.62,
		"price" : 15.99,
		"categorykey" : 1,
		"categoryname" : "Audio",
		"subcategorykey" : 101,
		"subcategoryname" : "MP4&amp;MP3",
		"dbt_scd_id" : "11d26c8c79e5e304ec85f86913a440b2",
		"dbt_updated_at" : "2025-01-17T00:37:05.581Z",
		"dbt_valid_from" : "2025-01-17T00:37:05.581Z",
		"dbt_valid_to" : null
	},
	{
		"productkey" : 2,
		"productcode" : 101002,
		"productname" : "Contoso 512MB MP3 Player E52 Blue",
		"manufacturer" : "Contoso, Ltd",
		"brand" : "Contoso",
		"color" : "Blue",
		"weightunit" : "ounces",
		"weight" : 4.1,
		"cost" : 6.62,
		"price" : 15.99,
		"categorykey" : 1,
		"categoryname" : "Audio",
		"subcategorykey" : 101,
		"subcategoryname" : "MP4&amp;MP3",
		"dbt_scd_id" : "fd80b5a5d49a315dea7ca9f302065ec7",
		"dbt_updated_at" : "2025-01-17T00:37:05.581Z",
		"dbt_valid_from" : "2025-01-17T00:37:05.581Z",
		"dbt_valid_to" : null
	}
]}</code></pre>



<p>Note the "dbt*" fields added to each record here:</p>



<ul class="wp-block-list">
<li>dbt_sct_id - a computed unique value based on the primary key and updated-at.</li>



<li>dbt_updated_at - this is when dbt last touched this record.  In our case, "touch" is alwasys going to be "add".</li>



<li>dbt_valid_from - this is the beginng of the effective date window for each record.  In the example above, note how dbt_valid_from is set to "now" when the model was initially built.  If you need to adjust that value to synthetically backdate initial records to a datetime.min value, you'd need to do this in a subsequent update. </li>



<li>dbt_valid_to - this is the end date for the effective date window.  Note that for the current record, this value is left null to indicate no expiration date.  It won't be set until / unless a new version of that record is created.</li>
</ul>



<h2 class="wp-block-heading">Limits of SCD</h2>



<p>The example above is an illustration of synthesizing a type-2 SCD records in a worst-case scenario, and as such, it shows some of the pitfalls of this approach.  The biggest issue, of course, is that the assignment of effective dates (begin and end) are tied directly to when (and how often) dbt is run.  This can lead to some mis-aligned time-based relationships in the warehouse, as shown here:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="333" data-attachment-id="2584" data-permalink="http://blog.componentoriented.com/2025/01/simulating-cdc-with-dbt-snapshots/untitled-diagram-2025-01-22-224612/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?fit=3840%2C2438" data-orig-size="3840,2438" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Untitled diagram-2025-01-22-224612" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?fit=300%2C190" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?fit=525%2C333" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?resize=525%2C333" alt="" class="wp-image-2584" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?resize=1024%2C650 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?resize=300%2C190 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?resize=768%2C488 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?resize=1536%2C975 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2025/01/Untitled-diagram-2025-01-22-224612.png?resize=2048%2C1300 2048w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>If your source data happens to have a last-updated date, of course, you can use the "timestamp" strategy in dbt's snapshot configuration, which narrows the window for problems, but won't eliminate it.  One of the keys here is to remember that this is a strategy for slowly-changing dimensions - the more active these data sets become, the more susceptible you are to errors.</p>



<p>If you need better accuracy for quickly-changing dimensions, consider modeling relationships in your transactional data.  This is something you can see to an extent in the Sales table of the Contoso dataset, where prices and costs of products are saved right in the sales transaction.  This captures the attributes that are subject to change in the Product dataset and are also of interest to Sales, reducing the need to link to a specific version of a Product.  You can also use a near-realtime CDC tool like Fivetran, which is capable of monitoring source datasets for changes and triggering data events that can feed your warehouse with a much higher fidelity than the solution shown here.  Any of these more accurate techniques come with a cost, though, so it's worth keeping this dbt option in your tool box.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2581</post-id>	</item>
		<item>
		<title>ChatGPT-assisted analysis</title>
		<link>http://blog.componentoriented.com/2024/12/chatgpt-assisted-analysis/</link>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 02:55:55 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[ChatGPT]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[software development]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2575</guid>

					<description><![CDATA[I was looking through a recording of a web page download in my browser's developer tools the other day, and began ticking off a number of calls to third-party domains. Before you know it, I had opened a document and started counting calls to domains, and it occurred to me -- perhaps ChatGPT could help &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2024/12/chatgpt-assisted-analysis/" class="more-link">Continue reading<span class="screen-reader-text"> "ChatGPT-assisted analysis"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>I was looking through a recording of a web page download in my browser's developer tools the other day, and began ticking off a number of calls to third-party domains.  Before you know it, I had opened a document and started counting calls to domains, and it occurred to me -- perhaps ChatGPT could help with this.</p>



<p>So, I opened it up and began asking questions:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Are you able to analyze the page load requests of a web site and summarize calls to third-party web sites (with descriptions of those sites)?</p>
</blockquote>



<p>And yes, it turns out, it was able to help.  It asked me to upload a HAR file (with instructions about how to generate and save it in developer tools), and began to analyze per my request.   I went back and forth a couple times, asking for clarification and tweaks, and after four or five rounds, ChatGPT let me know I'd used up my free analysis allocation.</p>



<p>But then, I noticed a little link the bot had been leaving for me, like a trail of breadcrumbs:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="77" data-attachment-id="2576" data-permalink="http://blog.componentoriented.com/2024/12/chatgpt-assisted-analysis/image-4/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?fit=1815%2C265" data-orig-size="1815,265" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?fit=300%2C44" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?fit=525%2C77" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?resize=525%2C77" alt="" class="wp-image-2576" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?resize=1024%2C150 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?resize=300%2C44 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?resize=768%2C112 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?resize=1536%2C224 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image.png?w=1815 1815w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>This link popped up a code block that contained the actual analysis for each step:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="388" data-attachment-id="2577" data-permalink="http://blog.componentoriented.com/2024/12/chatgpt-assisted-analysis/image-1-3/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?fit=1857%2C1372" data-orig-size="1857,1372" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?fit=300%2C222" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?fit=525%2C388" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?resize=525%2C388" alt="" class="wp-image-2577" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?resize=1024%2C757 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?resize=300%2C222 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?resize=768%2C567 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?resize=1536%2C1135 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2024/12/image-1.png?w=1857 1857w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>Going all the way back to the beginning of my chat, grabbing these steps, and dropping into a jupyter notebook, I found they ran almost as-is.  Just a couple tweaks to the HAR file load locations were needed to make these scripts work in my local environment.</p>



<p>There are defintitely still adjustments you'd make when investigating specific challenges with / for specific web sites, but this experience demonstrates a few powerful principles:</p>



<ol class="wp-block-list">
<li>Use of ChatGPT for analysis.  This isn't news to most, but I was quite impressed by the speed with which ChatGPT made progress on this problem.  With no code written on my part, ChatGPT made enough progress to help me understand more about my problem quite quickly.</li>



<li>Use of ChatGPT for code generation.  Seeing the code generation popping out of the analysis steps without having specifically asked for it -- that was new to me, and a very pleasant surprise.  Being fairly new to Python development, it was helpful to see this code as examples of the analysis techniques, including...</li>



<li>Use of HAR traces for website analysis.  Seeing this chewed up by the example Python code was a helpful illustration of this technique.</li>
</ol>



<p>If you're interested in seeing a jupyter notebook illustrating this example, you can find it here:<br><a href="https://github.com/dlambert-personal/gpt_assisted_har_analysis">https://github.com/dlambert-personal/gpt_assisted_har_analysis</a></p>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2575</post-id>	</item>
		<item>
		<title>UX Lessons from Visual Studio</title>
		<link>http://blog.componentoriented.com/2023/11/ux-lessons-from-visual-studio/</link>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Fri, 10 Nov 2023 16:57:38 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[usability]]></category>
		<category><![CDATA[UX]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2557</guid>

					<description><![CDATA[Commonly, when people think "user experience", they think about screen designs. Apps, web pages, Figma -- all that stuff. The shift from UI design to UX alone is a nod to this practice being more than skin-deep, but I still think a lot of the deeper behavioral aspects of user satisfaction are lost on many &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2023/11/ux-lessons-from-visual-studio/" class="more-link">Continue reading<span class="screen-reader-text"> "UX Lessons from Visual Studio"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>Commonly, when people think "user experience", they think about screen designs.  Apps, web pages, Figma -- all that stuff.  The shift from UI design to UX alone is a nod to this practice being more than skin-deep, but I still think a lot of the deeper behavioral aspects of user satisfaction are lost on many -- especially people who have only a casual interest in UI/UX.</p>



<p>I believe there are clues all around us, though, that we can take advantage of if we pay attention.  I'll begin with the premise / reminder that real usability is completely invisible.  You can only see usability by the absence of impediments to usability.  In short, a system is usable when it <a href="http://blog.componentoriented.com/2023/10/expected-and-actual/">does exactly what a user expects</a>, even if they can't articulate those expectations.</p>



<p>One of the best examples of invisible usability can be found in Visual Studio, which has been a market-leading IDE for decades now.  Built by developers for developers, they've gotten a lot of things right, and even though you're probably not building a tool for use by developers, some of these lessons likely can apply for you, too. </p>



<h2 class="wp-block-heading">Starting Fresh</h2>



<p>Right off the bat, Visual Studio shows its usability by setting users up to be successful when working with a new project.  Pick any template you like in Visual Studio, create a new project from it, and it will build successfully.  While it might not seem like a big deal, it makes a lot of difference for a new user learning one of these technologies.  The ability to start from a solid foundation gives that user confidence to build on that new solution. </p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="138" data-attachment-id="2569" data-permalink="http://blog.componentoriented.com/image-3/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?fit=1652%2C434" data-orig-size="1652,434" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image (3)" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?fit=300%2C79" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?fit=525%2C138" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?resize=525%2C138" alt="" class="wp-image-2569" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?resize=1024%2C269 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?resize=300%2C79 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?resize=768%2C202 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?resize=1536%2C404 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-3.png?w=1652 1652w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p><strong><em>What's the lesson?</em></strong>  Be aware of the "getting started" experience for your users.  Anything you can do to start a user more quickly and with more confidence will help them start using your software with confidence.  When users get started with confidence and gain momentum, they're more likely to persevere a bit more if they get stuck later, too.</p>



<h2 class="wp-block-heading">Save Always</h2>



<p>Another subtle way Visual Studio helps is one you probably never gave a second thought.  No matter what state your working files are in, you'll never mess one up so badly that Visual Studio won't let you save your work:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="90" data-attachment-id="2568" data-permalink="http://blog.componentoriented.com/2023/11/ux-lessons-from-visual-studio/image-1-2/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?fit=1783%2C305" data-orig-size="1783,305" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?fit=300%2C51" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?fit=525%2C90" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?resize=525%2C90" alt="" class="wp-image-2568" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?resize=1024%2C175 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?resize=300%2C51 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?resize=768%2C131 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?resize=1536%2C263 1536w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image-1.png?w=1783 1783w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>A more elegant take on this is to save automatically, so if something goes really wrong, you have a recovered version of your work (which Visual Studio obviously does).  Note that automatically saving can actually introduce some small usability hiccups of its own, but that's a problem for another day.   </p>



<p><strong><em>What's the lesson?</em></strong>  Document-based applications like Visual Studio and office apps (spreadsheets, docs and the like) typically consider this sort of behavior table stakes these days, and it's not likely you're building one of these, but watch out for behaviors that cause a user to not be able to save work-in-progress.  In a UI, you may run into this if you've got a bunch of required fields on a page, and as the field count goes up (and maybe the page count, too), expect that your users' frustration level will go up, too.</p>



<p>It's actually a lot more likely that you'll run into this problem in your APIs.  For example, here's a simple method I used in a post about <a href="http://blog.componentoriented.com/2023/10/dyamic-validation-using-fluentvalidation/" data-type="link" data-id="http://blog.componentoriented.com/2023/10/dyamic-validation-using-fluentvalidation/">fluent validation</a>:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
        public ActionResult CreateDealer(Dealer dealer)
        {
            Guard.Against.AgainstExpression&lt;int&gt;(id =&gt; id &lt;= 0, dealer.Id, &quot;Do not supply ID for new entity.&quot;); 

            var dvalidator = new DealerValidator();
            var result = dvalidator.Validate(dealer);
            if (result.IsValid)
            {
                _sampleData.Add(dealer);  // SaveChangesAsync() when working with an actual data store
                return CreatedAtAction(nameof(CreateDealer), new { id = dealer.Id }, dealer);
            }
            else
            {
                return BadRequest(result);
            }
        }
</pre></div>


<p>While this method effectively validates inputs, the return results to the user aren't especially helpful, and as an object like this scales up in size, it's more likely that a consumer will have partial inputs that don't pass all validation criteria.  Rather than preventing this from saving altogether, consider structuring an object like this so that as many values as possible are optional for saving -- even if they're needed in order to have a "valid" object.</p>



<p>In this simple example, we can create a result type that appends a ValidationResult property to the model.  We'll return this instead of the "naked" object to show the validation propertied <em>next to</em> the model.  The result object is preferrable to embedding a ValidationResult in the model because this keeps the request intact -- there's no reason for the ValidationResult to be part of the CRUD requests.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
    public class ModelValidationResult&lt;M, V&gt; where V : AbstractValidator&lt;M&gt;, new()
    {
        public ModelValidationResult(M model)
        {
            this.Model = model; 
        }
        public M Model { get; private set; }
        public FluentValidation.Results.ValidationResult ValidationResult { get; set; }
        public bool Validate()
        {
            V validator = new V();
            this.ValidationResult = validator.Validate(this.Model);
            return ValidationResult.IsValid;
        }
    }
</pre></div>


<p>With Validate in the Dealer object, the controller method also becomes a bit simpler:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
         public ActionResult CreateDealer(Dealer dealer)
        {
            Guard.Against.AgainstExpression&lt;int&gt;(id =&gt; id &lt;= 0, dealer.Id, &quot;Do not supply ID for new entity.&quot;);

            ModelValidationResult&lt;Dealer, DealerValidator&gt; res = new ModelValidationResult&lt;Dealer, DealerValidator&gt;(dealer);
            res.Validate();
            _sampleData.Add(dealer);  // SaveChangesAsync() when working with an actual data store
            return CreatedAtAction(nameof(CreateDealer), new { id = dealer.Id }, res);
        }
</pre></div>


<p>Note that I left the guard clause in place here -- there will still be some validations that really need to prevent saving bad data, but now that these changes are in place, it's possible to save an object with incomplete data.</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="275" data-attachment-id="2572" data-permalink="http://blog.componentoriented.com/2023/11/ux-lessons-from-visual-studio/image-2/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?fit=1139%2C597" data-orig-size="1139,597" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?fit=300%2C157" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?fit=525%2C275" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?resize=525%2C275" alt="" class="wp-image-2572" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?resize=1024%2C537 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?resize=300%2C157 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?resize=768%2C403 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/11/image.png?w=1139 1139w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>This technique won't work in all cases, but consider it if you can support saving objects with a minimal set of data and check for a fully-populated object later.</p>



<h2 class="wp-block-heading">And more!</h2>



<p>If you view Visual Studio with an eye to borrowing UX techniques, you'll see more lessons like these - <a href="http://blog.componentoriented.com/2023/10/dyamic-validation-using-fluentvalidation/">the Dynamic Validation example</a> I covered earlier is an example.  Since you're probably not building an IDE or even a tool for developers, you'll need to interpret some of the techniques  liberally, but I assure you the lessons are there.  You may also see some negative usability examples -- in fact, sometimes these are easier to see because they get in our way and draw our attention.</p>



<p>If you're interested in source code for this examle, you can find it on Github:  <a href="https://github.com/dlambert-personal/CarDealer/tree/Guard-vs-validate">https://github.com/dlambert-personal/CarDealer/tree/Guard-vs-validate</a></p>



<p></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2557</post-id>	</item>
		<item>
		<title>Expected and Actual</title>
		<link>http://blog.componentoriented.com/2023/10/expected-and-actual/</link>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Tue, 31 Oct 2023 20:41:14 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[usability]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2558</guid>

					<description><![CDATA[I'm a fan of simple hooks to summarize complex ideas, and one of my favorite go-to's is simple, memorable and versatile: Expected == Actual Not much to look at by itself, but I've gotten a lot of mileage out of this in some useful contexts. You probably recognize one of the most obvious applications immediately: &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2023/10/expected-and-actual/" class="more-link">Continue reading<span class="screen-reader-text"> "Expected and Actual"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>I'm a  fan of simple hooks to summarize complex ideas, and one of my favorite go-to's is simple, memorable and versatile:</p>



<figure class="wp-block-pullquote"><blockquote><p><strong>Expected == Actual</strong></p></blockquote></figure>



<p>Not much to look at by itself, but I've gotten a lot of mileage out of this in some useful contexts.  You probably recognize one of the most obvious applications immediately: testing / QA.  We're well-trained in applying expected == actual in testing, and most bug-reporting contexts will  define a bug as a scenario in which actual behavior doesn't match expected behavior.</p>



<p>Even in this simplest application, all the value here is in how we exercise the framework, and I promise if you really work this equation, you'll have a better handle on bugs immediately.  Of the two sides of this equation, "expected" usually seems easier, but is almost always more difficult becuase we most of our expectations are unspoken.  If I had a dime for every bug report I've seen where "actual" was well-documented but "expected" existed only in the mind of the reporter, for instance, I'd be writing this from a beach in Hawaii.  </p>



<p>Of course, that's where the exercise begins to get interesting.  "What were you expecting"  is a great place to start here.  If you can trace expectations to real documented requirements, it's a short conversation (perhaps followed by another conversation to see why that scenario was missed in autometed testing).  It's not unheard-of, though for these bugs to occur in scenarios that weren't explicitly called-out in requirements.  If this is the case, be sure to examine "expected" to see if it's reasonable for most users to expect the same thing under those conditions, which is a great segue to my next favorite application of expected == actual.</p>



<p>I'm of the opinion that expected == actual is also a pretty good foundation for understanding usability.   I touched on this recently in a <a href="http://blog.componentoriented.com/2023/10/apis-have-usability-too/">post about API usability</a>, and it holds true generally, I believe.  In that post, I referenced&nbsp;<a href="https://www.amazon.com/Dont-Make-Think-Revisited-Usability-dp-0321965515/dp/0321965515/ref=dp_ob_title_bk">Steve Krug's&nbsp;<em>Don't Make Me Think</em></a>, which is both an excellent book and another great oversimplification of UX.  Both of these are built on the premise that whenever an application does what a user expects it to do, they don't have to think about it, and they consider it usable.</p>



<p>Mountains have been written about <em>how </em>to accomplish this, of course, and I'm not going to improve on the books that talk about those techniques, but as I pointed out with resepct to API usability, consistent behavior goes a long way.  Even devices or applications we consider highly usable have a short learning curve, but one a user is invested in that curve, it's tremendously helpful to leverage that learning consistently -- no surprises!</p>



<p>A final consideration that applies in both these areas - when you find a case where "expected" really isn't well-definded yet, and can't be easily-derived based on other use cases (consistency), this is a golden opportunity.  Rather than just taking their version of "expected" at face value, ask why they expected that (when possible).  You probably won't be able to invest in a full "<a href="https://www.liberatingstructures.com/3-nine-whys/">nine-why's</a>" exercise, but keep that idea in mind.  The more you understand about how those expectations formed, the more closesly you can emulate that thinking as you project other requirements.</p>



<p>Besides these examples, where else might you be able to apply "expected vs. actual"?</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2558</post-id>	</item>
		<item>
		<title>REST is for nouns</title>
		<link>http://blog.componentoriented.com/2023/10/rest-is-for-nouns/</link>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Wed, 25 Oct 2023 16:25:10 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[API design]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[RESTful]]></category>
		<category><![CDATA[usability]]></category>
		<category><![CDATA[verb]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2496</guid>

					<description><![CDATA[It's hard to believe that REST is over twenty years old now, and although the RESTful style of designing APIs isn't specifically limited to JSON payloads, this style has become associated with transformation from SOAP/XML to a lighter-weight style better-suited to web applications. The near ubiquity of REST at this point can allow us to &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2023/10/rest-is-for-nouns/" class="more-link">Continue reading<span class="screen-reader-text"> "REST is for nouns"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>It's hard to believe that REST is over twenty years old now, and although the RESTful style of designing APIs isn't specifically limited to JSON payloads, this style has become associated with transformation from SOAP/XML to a lighter-weight style better-suited to web applications.</p>



<p>The near ubiquity of REST at this point can allow us to slip into some designs that I don't believe are particularly well-suited for this style of API.  In this article, I'm going to briefly examine scenarios that work well in a RESTful style -- and why -- and for scenarios that aren't well-suited for REST, we'll look at some alternatives.</p>



<h2 class="wp-block-heading">A closer look at REST</h2>



<p>Most software developers will recognize a RESTful API, but for many, this is a "I'll know it when I see it" recognition.  There's no shortage of defintions on the web for REST APIs, and I'd encourage you to browse a few to see what commonalities emerge.   Among the most common traits are statelessness, cacheability, and most importantly, a resource-based interface.</p>



<p>The "resource-based interface" is key here - this is the bedrock of the "style" of a RESTful interface.  Let's work through a simple example -- say, a car dealer web service.  The GET methods here are predictable given a Dealer type:</p>



<figure class="wp-block-image size-large"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="97" data-attachment-id="2534" data-permalink="http://blog.componentoriented.com/2023/10/rest-is-for-nouns/image/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?fit=1166%2C215" data-orig-size="1166,215" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?fit=300%2C55" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?fit=525%2C97" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?resize=525%2C97" alt="" class="wp-image-2534" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?resize=1024%2C189 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?resize=300%2C55 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?resize=768%2C142 768w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image.png?w=1166 1166w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>Here, we've got a list of dealers and a GET for one dealer specified by ID.  This is REST at its simplest.  The resource here is the Dealer, and much of the fluency of REST comes from being able to predict not only how these APIs will work, but also the remainder of the major operations we'd expect.  So, here, GET /Dealer will return a list of all Dealers known to this service, and GET /Dealer/{id} looks for just one.</p>



<p>Without even looking at the API spec, we know what most of the remaining operations should be for Dealers:</p>



<ul class="wp-block-list">
<li>Add - we expect to be able to add new dealers with a POST and a dealer model.</li>



<li>Edit- we expect to POST to /Dealer/{id} with a dealer model to edit that entity.</li>



<li>Delete - not all API's will support this, but for if this one does, it would be a DELETE action at /Dealer/{id}.</li>



<li>Patch - again, where it's supported, it allows updates by specifying only fields that have changed, and again, we'd expect to find it at /Dealer/{id}.</li>
</ul>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="525" height="249" data-attachment-id="2535" data-permalink="http://blog.componentoriented.com/2023/10/rest-is-for-nouns/image-1/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?fit=884%2C419" data-orig-size="884,419" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="image-1" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?fit=300%2C142" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?fit=525%2C249" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?resize=525%2C249" alt="" class="wp-image-2535" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?w=884 884w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?resize=300%2C142 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/image-1.png?resize=768%2C364 768w" sizes="auto, (max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" /></a></figure>



<p>A big theme contributing to the predictability of REST (which is one of the major appeals) is that you (the designer / developer) determine the resource - Dealer in this case - and the main operations shown here are largely implied.  You design the noun, and REST supplies the verbs (they're even <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods">commonly referred to as HTTP verbs</a>).  Another way to look at these is to consider the resource as an object that's normally at rest (aka REST).  Changes to this resource happen only when an HTTP verb acts on the resource.</p>



<p>The tendancy for these resources to change only when impacted by an HTTP verb via this API also contributes to the effectiveness of caching in a RESTful API, as you'll recall from the definitions referenced earlier.</p>



<p>Less universal, but very helpful, in my opinion, for a RESTful interface, is for the resource to behave atomically.  Admittedly, the example we're using here is ultra-simplified, but when we act on a Dealer, the closer we can be to these guidelines, the easier it is for API consumers to predict how the API will perform (API's have usability, too!).  These guidelines are based on the premise that REST is a web-native design, and developers will expect the API to behave as they'd expect other HTTP resources to behave.</p>



<ul class="wp-block-list">
<li>Operations are deterministic and cohesive.  Ideally, each operation should stand on its own.  Domain objects manipulated with these methods will certainly be subject to validation -- as in the url validation shown here.  This is a local problem with the specific parameters sent on this call, and most API developers should have no trouble sorting out what they need to do to make this call work.<br> <img data-recalc-dims="1" loading="lazy" decoding="async" width="150" height="79" data-attachment-id="2537" data-permalink="http://blog.componentoriented.com/url-validation-in-api/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?fit=1113%2C587" data-orig-size="1113,587" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="url validation in api" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?fit=300%2C158" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?fit=525%2C277" class="wp-image-2537" style="width: 150px;" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?resize=150%2C79" alt="" srcset="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?w=1113 1113w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?resize=300%2C158 300w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?resize=1024%2C540 1024w, https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/url-validation-in-api.png?resize=768%2C405 768w" sizes="auto, (max-width: 150px) 100vw, 150px" /></li>



<li>Avoid "bell-ringing".  A REST operation should be done when the status is returned to the caller.  There will be cases where a changed resource will kick off a workflow, but monitor these carefully.  If I kick off a workflow because I changed a resource and I don't care about the workflow, it may still be ok, but watch out for cases where a caller kicks off a workflow that they care about -- this may not be suitable for a simple REST-syntax method.</li>



<li>Permissions apply to the whole resource.  Imagine that the combination of resource and verb is all the information you've got to determine whether you're authorizing the operation.  A user in a given role should ideally be able to create / edit / delete a resource or not. </li>
</ul>



<h2 class="wp-block-heading">Complications arise</h2>



<p>As API operations become more complex, it's common to see use cases that bend the ideals of those simple REST operations.  Many of these nuanced use cases can be shoehorned into a REST-like syntax.  This is quite common for simple variations on verbs like Search or modifications or overloads of Add/POST or Edit/PUT.  Watch for factors like these - they signal you're moving into API scenarios that require some special attention, and may be clues that you're not really working with RESTful methods anymore:</p>



<ul class="wp-block-list">
<li>Methods refer to a verb other than normal REST verbs.  Some of these (ex: search) can be very compatible with a RESTful syntax and style.  If you start running into bespoke verbs, consider switching to an RPC-like call.  This can still live in the same API  and use JSON for transport -- it's just going to be more verb-forward than noun-forward.  These calls are invaluable as your API becomes more complex, but be aware that you give up some of the automatic usability of noun-based REST because these bespoke verbs are unique to your use case and application! </li>



<li>Nouns begin to become complex.   Consider a trivially-simple car dealer model:</li>
</ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: csharp; title: ; notranslate">
        public int Id { get; set; }
        public string? Name { get; set; }
        public string? Description { get; set; }
        public string? Website {  get; set; }
</pre></div>


<p>This ultra-simple model works great in a RESTful API, but it clearly doesn't have enough detail to be useful.  As soon as you add more detail / complexity -- in this case, an array of brand affiliations -- the model becomes more difficult to use in the API.  This example works great in the domain model, but with the addition of one new BrandAffiliation collection, the JSON structure becomes much more difficult for an API user / developer:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
  {
    &quot;id&quot;: 3,
    &quot;name&quot;: &quot;Capital City Acura&quot;,
    &quot;description&quot;: &quot;desc&quot;,
    &quot;website&quot;: null,
    &quot;brandAffiliation&quot;: &#x5B;
      {
        &quot;parent&quot;: {
          &quot;name&quot;: &quot;Honda&quot;,
          &quot;id&quot;: 3
        },
        &quot;name&quot;: &quot;Acura&quot;,
        &quot;id&quot;: 1
      }
    ]
  }
</pre></div>


<p>A more friendly approach for the API user would likely be a nested REST structure that permits a get or post like /dealer/{dealerid}/parent/{parentid}.  If this is starting to look like an OData style, that's great - that's a direction we'll explore more in the future!</p>



<h2 class="wp-block-heading">Beyond Simple REST</h2>



<p>In some cases, complex or complicated scenarios may be better-suited with an RPC-style method.  These can live in a predominantly RESTful API - nothing about these is incompatible with the API specifications governing REST, but I think it's helpful to be aware when you're moving away from a pure REST model.  Amazon has published a <a href="https://aws.amazon.com/compare/the-difference-between-rpc-and-rest/" data-type="link" data-id="https://aws.amazon.com/compare/the-difference-between-rpc-and-rest/">great summary of REST vs. RPC</a>, and it's a great place to start in considering these styles.  For our purposes, it's important to recognize that "RPC-like" in style does not imply an actual RPC API!</p>



<p>As use cases evolve beyond that simple REST/OData-like style we looked at earlier, here are some specific places where "simple REST" may not have enough gas in the tank:</p>



<ul class="wp-block-list">
<li>Verb-centric operations -- APIs where nouns participate, but operations are more about what's happening with those objects.  These can be good places to explore those RPC-like calls.</li>



<li>Operations tying multiple objects or object types together (likely in a non-heirarchical way).  In some cases,  graph-ql APIs can fall in this category.</li>



<li>Anything that begins to take on state-machine characteristics -- I consider this a special form of verb-centric.</li>



<li>Events.  Depending on your use case and how deep you're diving into the event pool, these could wind up looking RPC-like or you may be interested in something like <a href="https://editor-next.swagger.io/" data-type="link" data-id="https://editor-next.swagger.io/">Async API</a>.</li>
</ul>



<p>In all these cases, be sure to be aware of <a href="http://blog.componentoriented.com/2023/10/apis-have-usability-too/" data-type="link" data-id="http://blog.componentoriented.com/2023/10/apis-have-usability-too/">discoverability and predictabilty</a> of your API.  In many cases, it can be worth shoehorning an operation into a noun-centric REST call for the sake of consistency.   I'll explore some of these nuances in future posts, including places where CQRS meets events and API styles.</p>



<p>In the meantime, try paying attention to nouns &amp; verbs in your API and see if that helps guide some decisions about API style.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2496</post-id>	</item>
		<item>
		<title>APIs have usability, too!</title>
		<link>http://blog.componentoriented.com/2023/10/apis-have-usability-too/</link>
					<comments>http://blog.componentoriented.com/2023/10/apis-have-usability-too/#comments</comments>
		
		<dc:creator><![CDATA[dlambert]]></dc:creator>
		<pubDate>Tue, 24 Oct 2023 16:04:57 +0000</pubDate>
				<category><![CDATA[Software Development]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[API design]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[software development]]></category>
		<category><![CDATA[usability]]></category>
		<category><![CDATA[UX]]></category>
		<guid isPermaLink="false">http://blog.componentoriented.com/?p=2543</guid>

					<description><![CDATA[Usability has a long history in software. In fact, as I sat down to pen these words, I googled "history of usability ux" and turned up some scholarly articles going back over 100 years. Too far. In software, you can't go wrong starting with Apple, which puts the origin of UX in the mid 90's. &#8230; <p class="link-more"><a href="http://blog.componentoriented.com/2023/10/apis-have-usability-too/" class="more-link">Continue reading<span class="screen-reader-text"> "APIs have usability, too!"</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>Usability has a long history in software.  In fact, as I sat down to pen these words, I googled "history of usability ux" and turned up some scholarly articles going back over 100 years.  Too far.   In software, you can't go wrong starting with Apple, which puts the origin of UX in <a href="https://www.nngroup.com/articles/100-years-ux/" data-type="link" data-id="https://www.nngroup.com/articles/100-years-ux/">the mid 90's</a>.  Better.</p>



<p>But for much of this time, we've tuned in to software usability as experienced through our user interfaces by end-users.  Today, there's more to usability than user interface design, and I'd like to broaden the discussion a bit.</p>



<h2 class="wp-block-heading">Usability?  What usability?</h2>



<p>When you think about great user experiences, typically, we don't consider them to be great user experiences unless we start comparing them to lesser experiences.  I believe this is a big clue into how we can apply UX more universally.  I really think a lot of usability boils down to doing what a user is expecting you to do... so when you do it, most users will never even notice.</p>



<p>Think about it - when's the last time you gave a passing thought to usability for an application that was already behaving the way you wanted?  There are scores of great books on how to achieve usability (I love <a href="https://www.amazon.com/Dont-Make-Think-Revisited-Usability-dp-0321965515/dp/0321965515/ref=dp_ob_title_bk" data-type="link" data-id="https://www.amazon.com/Dont-Make-Think-Revisited-Usability-dp-0321965515/dp/0321965515/ref=dp_ob_title_bk">Steve Krug's <em>Don't Make Me Think</em></a>, and <a href="https://www.amazon.com/Design-Everyday-Things-Revised-Expanded/dp/0465050654/ref=asc_df_0465050654/?tag=hyprod-20&amp;linkCode=df0&amp;hvadid=312106851030&amp;hvpos=&amp;hvnetw=g&amp;hvrand=11337518577235281264&amp;hvpone=&amp;hvptwo=&amp;hvqmt=&amp;hvdev=c&amp;hvdvcmdl=&amp;hvlocint=&amp;hvlocphy=9014898&amp;hvtargid=pla-416263148589&amp;psc=1" data-type="link" data-id="https://www.amazon.com/Design-Everyday-Things-Revised-Expanded/dp/0465050654/ref=asc_df_0465050654/?tag=hyprod-20&amp;linkCode=df0&amp;hvadid=312106851030&amp;hvpos=&amp;hvnetw=g&amp;hvrand=11337518577235281264&amp;hvpone=&amp;hvptwo=&amp;hvqmt=&amp;hvdev=c&amp;hvdvcmdl=&amp;hvlocint=&amp;hvlocphy=9014898&amp;hvtargid=pla-416263148589&amp;psc=1">Don Norman's <em>The Design Of Everyday Things</em></a>), but I really believe if you can manage to do what the user is expecting you to do, you're typically in pretty good shape.</p>



<h2 class="wp-block-heading">The changing landscape of applications</h2>



<p>Next, let's look at changes in applications and application design.  You can probably see where this is going.  Whereas once the only users of our applications were end-users operating via a Windows or web interface, cloud-native applications based on microservice architecture rely heavily on APIs to orchestrate, integrate and extend functionality.  In many cases, APIs <strong><em>are </em></strong>the interfaces for services, and developers are our users.  In this sense, APIs are the interface, and the user experience (UX) is found in the ease-of use of these APIs.</p>



<p>And what is it about an API we'd consider more usable?  As with the generalized case above, I think the ultimate yardstick is whether the API behaves the way a developer would expect.  I believe the popularity of RESTful APIs, for instance, isn't just because JSON is easy to work with -- it's because a well-written RESTful API is <em>discoverable</em> and <em>predictable</em>. </p>



<p>Note that <em>discoverable </em>isn't the same as documented - even correctly documented, which never happens.  Discoverable starts with tools like swagger that expose live documentation of API methods and objects, but it connects with <em>predicatable </em>in an important way: as developers engage with your API and discover how some of it works, consistent behavior and naming creates predicability.  When these two factors are combined, they reinforce one another and create an upward spiral for developers in which learning is rewarded and also helps future productivity.  And yes - this exact relationship is part of understanding usability in a visual / UX context, as well.</p>



<figure class="wp-block-image size-full"><a href="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/Usability-venn.drawio.png"><img data-recalc-dims="1" loading="lazy" decoding="async" width="280" height="171" data-attachment-id="2549" data-permalink="http://blog.componentoriented.com/usability-venn-drawio/" data-orig-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/Usability-venn.drawio.png?fit=280%2C171" data-orig-size="280,171" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Usability venn.drawio" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/Usability-venn.drawio.png?fit=280%2C171" data-large-file="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/Usability-venn.drawio.png?fit=280%2C171" src="https://i0.wp.com/blog.componentoriented.com/wp-content/uploads/2023/10/Usability-venn.drawio.png?resize=280%2C171" alt="" class="wp-image-2549"/></a></figure>



<p></p>



<p>Watch for more posts on API conventions and style soon, and watch for the ways these ideas support one another and ultimately contribute to Krug's tagline:<em> Don't make me think!</em></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>http://blog.componentoriented.com/2023/10/apis-have-usability-too/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2543</post-id>	</item>
	</channel>
</rss>
