<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Uniting art and engineering in code]]></title><description><![CDATA[Uniting art and engineering in code]]></description><link>https://blog.encodeart.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 14:12:24 GMT</lastBuildDate><atom:link href="https://blog.encodeart.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Superforms v2: Supporting all validation libraries]]></title><description><![CDATA[Superforms is a popular SvelteKit library that simplifies numerous aspects of form handling: Validation, error handling, nested data, loading spinners, browser constraints, and much more. About a month after the announcement that Superforms won Svelt...]]></description><link>https://blog.encodeart.dev/superforms-v2-supporting-all-validation-libraries</link><guid isPermaLink="true">https://blog.encodeart.dev/superforms-v2-supporting-all-validation-libraries</guid><category><![CDATA[forms]]></category><category><![CDATA[form validation]]></category><category><![CDATA[Sveltekit]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[Validation]]></category><category><![CDATA[json-schema]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Fri, 01 Dec 2023 09:00:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701405106126/9b08d7c0-4878-43d7-a528-dc91e233ad00.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://superforms.rocks/">Superforms</a> is a popular SvelteKit library that simplifies numerous aspects of form handling: Validation, error handling, nested data, loading spinners, browser constraints, and much more. About a month after the announcement that Superforms <a target="_blank" href="https://hack.sveltesociety.dev/winners">won Svelte Hack 2023</a>, version 1.0 was released (in June 2023), and it has been a quite stable journey since then.</p>
<p>Thanks to a comprehensive test suite and browser automation testing, over 170 issues have been closed on Github with almost no regressions, and the new features added from 1.0 to the current 1.11 are always non-intrusive and backward-compatible.</p>
<p>This is much thanks to the simple API, which works on the deconstruct principle that you only expose what you need. So when you call <code>superForm</code> (basically the only API you need on the client), the initial call is very simple:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Only deconstruct what you need</span>
<span class="hljs-keyword">const</span> { form } = superForm(data.form);
</code></pre>
<p>And as you move along and introduce additional features like client-side validation, error handling, loading spinners, you just keep deconstructing what you need, together with configuration as an optional parameter:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { form, errors, enhance, delayed } = superForm(data.form, {
  resetForm: <span class="hljs-literal">true</span>
});
</code></pre>
<p>This encapsulates the whole form behavior within a single function call, making things simple to both understand and expand upon.</p>
<p>So in general, all is well with version 1, but there's one thing that comes up now and then, touching on a problem that, when you enter the rabbit hole, goes quite deep.</p>
<p>This text is written in two parts, the first one is the background to the problem, and the second is how to solve it.</p>
<h1 id="heading-part-1-the-already-solved-problem">Part 1: The already-solved problem</h1>
<p>Looking at the numerous validation libraries out there, it's obvious that validation is quite a popular problem to tackle, having led to a friendly competition where the focus seems to be mostly on performance and bundle size. That is certainly a too simple view in most cases, so I apologize, but I say it because <strong>the problem itself - validation - has already been solved</strong>.</p>
<p>Most validation libraries don't add any brand-new features. Instead, they bring higher speed, smaller size and a different syntax, where at least the first two can be useful, unless it's the evil premature optimization.</p>
<p>More could be said about this, but even if we accept that people fill the ecosystem with similar libraries and consider that a good thing for everyone, there's a deeper issue at hand.</p>
<h2 id="heading-schema-validation-vs-form-validation">Schema validation vs. Form validation</h2>
<p>A frequent question for Superforms is <em>"Does it support validation library X?"</em> If it did, this writing wouldn't exist, so naturally the answer is no, it only supports <a target="_blank" href="https://zod.dev/">Zod</a>. This can be disappointing, given that there are smaller and faster libraries out there. So am I a Zod fanboy, made a big oversight, or is there another reason for not supporting any other library?</p>
<p>I do think Zod is great, but I did plenty of research before settling on it because there's a big difference between a <em>schema validation</em> library, which just handles validating data, and a <em>form validation</em> library like Superforms, which must handle numerous other aspects of the validation process. What others? Let's take a look at a rather overlooked one, type safety.</p>
<h2 id="heading-schema-type-safety">Schema type safety</h2>
<p>The idea with Superforms is that the validation schema encapsulates a single form, so the schema can be a single source of truth for the form, including its data, which type can be inferred from the schema. This means that if you have a schema like:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> schema = z.object({
  name: z.string().min(<span class="hljs-number">2</span>)
  email: z.string().email()
})
</code></pre>
<p>The inferred type of the data is:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> Schema = z.infer&lt;<span class="hljs-keyword">typeof</span> schema&gt;;

<span class="hljs-comment">// Resulting type:</span>
{
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>Note that the types here are <code>string</code>, not <code>string | undefined</code>, since the fields aren't optional in the schema. So when you're using this data, the fields <em>must</em> be set to a string. You can't just use an empty object, any operation on the supposed string data will then fail.</p>
<p>Some validation libraries solve this with default values, but adding that to every field is rather tedious:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> schema = z.object({
  name: z.string().min(<span class="hljs-number">2</span>).default(<span class="hljs-string">''</span>)
  email: z.string().email().default(<span class="hljs-string">''</span>)
  score: z.number().default(<span class="hljs-number">0</span>)
  <span class="hljs-comment">// ...and 10+ more fields</span>
})
</code></pre>
<p>Others don't care, as they just focus on the data validation. In either case, it puts an additional burden on the library consumer - supply default values, or your data won't be type-safe.</p>
<p>Having an extra data structure with default values for every schema is not only tedious but redundant since <em>the information is already there</em>. Looking at the schema, you know that name and email are strings. But how to access this information programmatically? This leads us to the problem at hand: <strong>You must be able to introspect the schema, to avoid burdening the user</strong>.</p>
<h2 id="heading-introspection">Introspection</h2>
<p><em>Introspection</em> is the ability to examine the type or properties of an object at runtime. A related concept is <em>reflection</em>, which goes a step further and also allows manipulation of these properties.</p>
<p>This is essential for form validation because if it would be possible to introspect on the above schema, we could loop through the properties and see that <code>name</code> is a <code>string</code>, and then set a default value, an empty string in this case, that makes the data type-safe. No burden on the user, no additional data structure.</p>
<p>You may have figured out the problem already - which of the popular validation libraries support introspection?</p>
<p>Assuming that it must be a documented and quite stable feature, the answer is, as of November 2023, not many except Zod do.</p>
<p>Some libraries like <a target="_blank" href="https://ajv.js.org/">Ajv</a> are using <a target="_blank" href="https://json-schema.org/">JSON Schema</a>, which is a formidable attempt at a validation standard, self-reflecting since it's just JSON. Compared to the fluent syntax of a validation library with IDE autocompletion, it can appear quite verbose though. Compare this Zod schema:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> schema = z.object({
  name: z.string().min(<span class="hljs-number">2</span>)
  email: z.string().email()
})
</code></pre>
<p>To the equivalent JSON Schema:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"object"</span>,
  <span class="hljs-attr">"properties"</span>: {
    <span class="hljs-attr">"name"</span>: { <span class="hljs-attr">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-attr">"minLength"</span>: <span class="hljs-number">2</span> },
    <span class="hljs-attr">"email"</span>: { <span class="hljs-attr">"type"</span>: <span class="hljs-string">"string"</span>, <span class="hljs-attr">"format"</span>: <span class="hljs-string">"email"</span> }
  },
  <span class="hljs-attr">"required"</span>: [ <span class="hljs-string">"name"</span>, <span class="hljs-string">"email"</span> ],
  <span class="hljs-attr">"additionalProperties"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"$schema"</span>: <span class="hljs-string">"http://json-schema.org/draft-07/schema#"</span>
}
</code></pre>
<p>Because of that, libraries like <a target="_blank" href="https://github.com/sinclairzx81/typebox">Typebox</a> have been created to simplify JSON Schema generation. But wouldn't it be nice to use only your favorite library, instead of having to jump through the hoops of</p>
<pre><code class="lang-plaintext">Typebox → Ajv → Validate data → Get default values from JSON Schema → Type-safe form data
</code></pre>
<p>Zod makes this quite easy since you can access the schema as objects:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">defaultValue</span>(<span class="hljs-params">zodType: ZodTypeAny</span>) </span>{
  <span class="hljs-keyword">switch</span>(zodType._def.typeName) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'ZodString'</span>: <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">'ZodNumber'</span>: <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">'ZodObject'</span>: {
      <span class="hljs-keyword">const</span> zodObject = zodType <span class="hljs-keyword">as</span> AnyZodObject
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> [key, value] <span class="hljs-keyword">of</span> <span class="hljs-built_in">Object</span>.entries(zodObject.shape)) {
        <span class="hljs-comment">// Call defaultValue recursively to assign each property</span>
      }
    }
    <span class="hljs-comment">// ... and so on</span>
  }
}
</code></pre>
<p>(<code>typeName</code> is used instead of <code>instanceof</code> to avoid prototype mismatch, which can happen if packages use different versions of Zod. Kudos to the author Colin McDonnell for acknowledging this.)</p>
<p>Now, for smaller systems, a few default values or extra data structures for the schemas could be manageable. But it gets more problematic when we look at another aspect, one where introspection turns into a dire need.</p>
<h2 id="heading-constraint-validation-in-the-browser">Constraint validation in the browser</h2>
<p>HTML5 introduced <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation">constraint validation</a>, attributes on the form elements that improve validation by giving feedback in the browser, even if Javascript is disabled.</p>
<p>They can be easily added to any input field:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">required</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">minlength</span>=<span class="hljs-string">"2"</span> /&gt;</span>
</code></pre>
<p>However, given that we have a schema with the purpose of defining validation constraints, applying these manually on every field is also quite redundant. Mapping the schema to browser validation constraints would be quite preferable, since there are <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation#validation-related_attributes">plenty of attributes</a> to deal with. Min/max values for numbers, the essential <code>required</code> attribute, pattern matching, etc.</p>
<p>To do this, you need to be able to extract constraints based on every schema field. The <code>required</code> attribute is an especially good example since it requires knowledge about whether the field is optional or nullable.</p>
<p>With Superforms introspecting the Zod schema, you'll get this for free and can use the constraints just by spreading its variable on an input field:</p>
<pre><code class="lang-svelte"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> data;
  <span class="hljs-keyword">const</span> </span></span><span class="javascript">{ form, constraints }</span><span class="xml"> = superForm(data.form);
<span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

Name: <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">bind:value</span>=</span></span><span class="javascript">{$form.name}</span><span class="xml"><span class="hljs-tag"> </span></span><span class="javascript">{...$constraints.name}</span><span class="xml"><span class="hljs-tag"> /&gt;</span></span>
</code></pre>
<p>(Thanks Svelte for this cool feature!)</p>
<p>This is very convenient and useful, but puts some demands on the introspection capabilities of the validation library. Implementing that requires some planning beforehand, or a larger rewrite otherwise, so it may not be a high priority. But by not supporting introspection, the library is doing its users a considerable disservice.</p>
<h2 id="heading-the-library-vendor-lock-in">The library vendor lock-in</h2>
<p>Every validation library has its DSL (domain-specific language) for the domain-specific task of validating data. This DSL is also known as the schema syntax. The syntax itself is a part of the programming language and is not easily accessible at runtime, so we must rely on the library to expose the schema metadata through its API. If we cannot access and transform it into something else, suddenly your library of choice may force you to manually handle default values and constraints separately, even though you see everything you need to automate it, every time you look at the schema.</p>
<p>This is essentially the same as being committed and locked into a specific vendor, that refuses to open up a way of accessing its data or integrating its features.</p>
<p>That's why I'm strongly advocating that <strong>the popular validation libraries, and all future ones, must add introspection capabilities</strong>, even if it's more enjoyable to work on shaving microseconds off the benchmark comparison instead.</p>
<p>Of course, due to the nature of Open Source Software, it's not possible to demand this of the library maintainers. Most of us do it in our spare time, because it's enjoyable and interesting, like a hobby. But here it is in writing, at least, and if the point of publishing a library is about doing something good for others (otherwise, please don't publish it), I hope that adding a decidedly important feature is desirable as well as tinkering with the fun parts.</p>
<h1 id="heading-part-2-solving-the-problem">Part 2: Solving the problem</h1>
<p>To summarize part 1, the problem is as follows:</p>
<ol>
<li><p>We want to use a favorite validation library together with Superforms.</p>
</li>
<li><p>We want the data to be inferred from its schema, for type-safety and DX.</p>
</li>
<li><p>We don't want to deal with default values and constraints by hand - they should be derived from the schema at runtime.</p>
</li>
</ol>
<p>And of course, without feeling a bit demanding on the open-source contributors that work for free, we want all this even if our favorite validation library doesn't have any introspection capabilities! In which case we're prepared to make things a bit more redundant, to enjoy the speed, size and/or syntax of the chosen library.</p>
<p>This may look daunting, but here's the upside of OSS - there may be a lot of libraries out there, some similar to each other, but thanks to the sheer amount, there will also be libraries that solve a specific problem for almost anything.</p>
<h2 id="heading-problem-1-use-any-validation-library">Problem 1 - Use any validation library</h2>
<p>As mentioned, the "simple problem" of pure validation has been solved already. We want to take some data as input and get validation errors as output. As this is what validation is about at its core, it should be possible to unify most libraries to solve our first problem.</p>
<p>And it comes as no surprise that this has been made already, by a library called <a target="_blank" href="https://typeschema.com/">TypeSchema</a>. It has adapters currently for 14 validation libraries, unifying the validation process in a common API. If Superforms was only about error reporting, we'd be done by now. We're not, but this is still a tremendous help.</p>
<h2 id="heading-problem-2-type-inference">Problem 2 - Type inference</h2>
<p>What about problem 2, type inference for the form data? This is even simpler. TypeSchema handles it very well, as can be seen by <a target="_blank" href="https://github.com/decs/typeschema#coverage">its coverage table</a>. The rest, like transforming the inferred data type to an error type, is handled by Typescript and its <a target="_blank" href="https://www.typescriptlang.org/docs/handbook/2/mapped-types.html">mapped types</a>.</p>
<h2 id="heading-problem-3-introspection">Problem 3 - Introspection</h2>
<p>But we also need introspection. Is there a unified solution even for that?</p>
<p>Here's where <a target="_blank" href="https://json-schema.org/">JSON Schema</a> comes in as a true savior. If we can generate a JSON Schema for each library and its schemas, we have a standard to follow, which can be converted to default values and constraints with ease. This is how standards should be used, behind the scenes, so their verbose style won't get in the way of users. Many thanks to the JSON Schema creators for taking the time to painstakingly define - and thereby in detail solve - the problem of data validation.</p>
<p>And guess what, converting schemas back and forth has also been solved already! At least for a few libraries. Zod schemas can be converted to JSON Schema with a library not surprisingly called <a target="_blank" href="https://github.com/StefanTerdell/zod-to-json-schema">zod-to-json-schema</a>. Other libraries like Ajv are already using JSON Schema, so the schema can just be lifted from there. If needed, we could use another nice piece of software like <a target="_blank" href="https://github.com/ThomasAribart/json-schema-to-ts#readme">json-schema-to-ts</a> to infer the types directly from the JSON Schema.</p>
<p>For the others, the best would be to raise the issue at the respective library repo, so a JSON Schema exporter can be built in, or at least introspection capabilities so a converter can be made. Until that, coding the JSON Schema by hand is a tedious option, so as a middle way, a data structure with default values could be converted to a JSON Schema with <a target="_blank" href="https://github.com/ruzicka/to-json-schema#readme">to-json-schema</a>. There won't be any constraints with such a solution, but that may not always be a requirement anyway.</p>
<h1 id="heading-the-v2-api">The v2 API</h1>
<p>With all the problems solved, we can now formulate an API for <strong>Superforms version 2</strong> based on the capabilities of each validation library. The function used to validate data in Superforms is called <code>superValidate</code>. It takes 1-3 parameters:</p>
<ul>
<li><p><strong>schema:</strong> The validation schema.</p>
</li>
<li><p>[Optional] <strong>data:</strong> Data that should be validated, can be from a DB or FormData in an HTTP request. If missing, use default values.</p>
</li>
<li><p>[Optional] <strong>options:</strong> Extra options.</p>
</li>
</ul>
<h3 id="heading-case-1-library-has-introspection">Case 1: Library has introspection</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> form = <span class="hljs-keyword">await</span> superValidate(schema, data)
</code></pre>
<p>This is the best case, where everything is handled conveniently behind the scenes by mapping the schema metadata to JSON Schema.</p>
<h3 id="heading-case-2-no-introspection-but-json-schema-is-available">Case 2: No introspection, but JSON Schema is available.</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> form = <span class="hljs-keyword">await</span> superValidate(schema, data, { jsonSchema })
</code></pre>
<p>If a JSON Schema can be created by some other means, passing it as an extra option will ensure that default values and constraints are generated properly.</p>
<h3 id="heading-case-3-no-introspection-no-json-schema">Case 3: No introspection, no JSON Schema.</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> form = <span class="hljs-keyword">await</span> superValidate(schema, data, { defaults })
</code></pre>
<p>If a JSON Schema isn't available and there is no introspection, passing an object of default values will at least provide type safety. No constraints will be available, but everything else will work.</p>
<h2 id="heading-schema-detection-and-tree-shaking">Schema detection and tree shaking</h2>
<p>It can be difficult to detect which of the validation libraries the schema belongs to at runtime. Therefore, it may be useful to handle the introspection in an adapter function, which also would help with tree shaking, quite important when we're about to support 14+ validation libraries. We only want to include the runtime for the library being used. Here's what it could look like:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { zod } <span class="hljs-keyword">from</span> <span class="hljs-string">'sveltekit-superforms/adapters'</span>

<span class="hljs-keyword">const</span> form = <span class="hljs-keyword">await</span> superValidate(zod(schema), data)
</code></pre>
<p>These ideas are not final, and help is much appreciated. Please open an issue at the <a target="_blank" href="https://github.com/ciscoheat/sveltekit-superforms">repo on Github</a>, or join the Discord server so we can have a chat in the <a target="_blank" href="https://discord.gg/zH4XJR4sZQ">#v2 channel</a>. There are also a few <a target="_blank" href="https://github.com/ciscoheat/sveltekit-superforms/issues?q=is%3Aopen+is%3Aissue+milestone%3Av2.0">2.0 milestone issues</a> that can be worth checking out.</p>
<h1 id="heading-roadmap-to-version-2">Roadmap to version 2</h1>
<p>There we have the plan for making Superforms support almost any validation library! A few tests have been made already and it looks like there should be no problems in making it happen.</p>
<p>I estimate that the validation part, <code>superValidate</code>, will be finished rather quickly, as the other libraries will do most of the heavy lifting there. The client part, <code>superForm</code>, is more intricate but a lot will be reused from version 1. Some parts are still dependent on Zod or have to be rewritten though, like the client-side validation, because they are frankly a bit messy. The test suite and browser automation tests must also be ported.</p>
<h2 id="heading-donate-to-make-it-happen">Donate to make it happen</h2>
<p>Unfortunately, there's not enough time and money to do all this rapidly. Maintaining and giving support for version 1 takes up a bit of time already, but with donations, I can at least distribute the time and prioritize the development.</p>
<p>So if you want to see Superforms v2 happen, there are a <a target="_blank" href="https://superforms.rocks/contributing">few ways of donating</a> on the Superforms website. Any amount helps, and a monthly donation of $10 or more will be listed on the <a target="_blank" href="https://superforms.rocks/sponsors">Sponsors</a> page. A huge thanks to the people that are sponsoring, or have donated in the past.</p>
<p>Whether you donate or not, you are welcome to the <a target="_blank" href="https://discord.gg/AptebvVuhB">Discord server</a> to have a chat, give a word of encouragement, and of course get help for the current version of Superforms. See you there!</p>
]]></content:encoded></item><item><title><![CDATA[DCI tutorial for TypeScript: Part 5]]></title><description><![CDATA[Welcome back to the DCI series! Here are the previous parts if you're new.
Error handling in Contexts
As promised, the topic for today is error handling with the least amount of surprise. This could lead us to ask what is surprising when coding and d...]]></description><link>https://blog.encodeart.dev/dci-tutorial-for-typescript-part-5</link><guid isPermaLink="true">https://blog.encodeart.dev/dci-tutorial-for-typescript-part-5</guid><category><![CDATA[dci]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[error handling]]></category><category><![CDATA[exceptionhandling]]></category><category><![CDATA[events]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Mon, 24 Jul 2023 19:05:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667741895079/BvO1cA-US.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back to the DCI series! <a target="_blank" href="https://blog.encodeart.dev/series/dci-typescript-tutorial">Here are the previous parts</a> if you're new.</p>
<h1 id="heading-error-handling-in-contexts">Error handling in Contexts</h1>
<p>As promised, the topic for today is <strong>error handling with the least amount of surprise</strong>. This could lead us to ask what is surprising when coding and debugging.</p>
<h2 id="heading-code-locality">Code locality</h2>
<p>DCI does an excellent job at <em>locality -</em> making the code understandable when looking at only a small portion of it. In the non-polymorphic, pattern-less locale of a DCI Context, you rarely have to seek outside it to understand how things are supposed to work.</p>
<p>Let's illustrate this with an example of the opposite. There is a problem with password handling in a web framework, and we have to figure it out. The entry point is found, but then things get a bit abstract:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PasswordController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span>
</span>{
  <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params">Request $request, UpdatesUserPasswords $updater</span>)
  </span>{
    $updater-&gt;update($request-&gt;user(), $request-&gt;all());
    <span class="hljs-keyword">return</span> app(PasswordUpdateResponse::class);
  }
}
</code></pre>
<p>We see a short controller class that calls an updater. <code>UpdatesUserPasswords</code> is an interface, so next we have to find the implementations, briefly analyze them and start debugging to conclude which one is the problem. And then the process repeats, maybe the <code>UpdatesUserPasswords</code> implementation calls an <code>UpdatesClient</code> and an <code>UpdatesDatabase</code> interface, with respective implementations, and so on... you'll get the idea.</p>
<p>This abstraction is not only unexplanatory (an "update" method with an "updater" that will "update") but will lead us down a rabbit hole. Where are things happening? In the next layer of abstraction? Or parts of it here, and the rest in another place?</p>
<p>DCI does the opposite, by describing local and contextualized behavior. A Role is an interface but related to relevant behavior. You can see it as the DCI Role abstraction points toward the architecture of the system, instead of towards some general "update" notion that conceals the system behavior and mental model.</p>
<h2 id="heading-the-21st-century-goto">The 21st-century GOTO</h2>
<p>Lack of locality can be confusing and lead to surprises, because the further we go away from a central location, the more we have to know about and mentally knit together all parts of the system - not a trivial task.</p>
<p>Now imagine that during all this, you're suddenly transported somewhere else and have to refocus and adjust to new surroundings.</p>
<p>This is what happens when a program jumps across reasonable boundaries, as in the following concepts:</p>
<ul>
<li><p>Events</p>
</li>
<li><p>Exceptions</p>
</li>
</ul>
<p>Arbitrary jumping across a program has since long been frowned upon, but it seems to have resurfaced in these concepts as a 21st-century GOTO. And we experience pretty much the same kind of surprise and confusion when getting lost in reality, as in program code: "Where am I? How did I get here?"</p>
<p>With DCI however, we have an effective way of protecting ourselves from these kinds of surprises, which is, as you may have guessed, to keep the program flow within the Context to the highest possible extent. Let's examine the "GOTOs" and see if and how they can be used in a DCI Context.</p>
<h2 id="heading-events">Events</h2>
<h3 id="heading-role-contract-events">Role contract events</h3>
<p>If a Role contract contains some event subscribing method, be aware that any subscription to it finds the Context program flow at the mercy of a Roleplayer. This can be confusing and also break the mental model described by the RoleMethods, as it can suddenly interrupt the defined message flow.</p>
<p>Therefore, if you need to subscribe to an event, treat it as a <em>one-off event</em> if possible. For example, <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener</a> has a <code>once</code> option. After the event is handled, you explicitly subscribe to it again, so it becomes a part of the message flow, keeping you in control of the program.</p>
<h3 id="heading-context-events">Context events</h3>
<p>Exposing events in the public Context interface should at least warrant the question of why it is needed. Since a Context can be a RolePlayer in another Context, by exposing an event source, we potentially cause surprises in other Contexts.</p>
<h2 id="heading-exceptions">Exceptions</h2>
<p>Jumping from the well-defined message flow of a Context to some unknown place outside it violates the readability goal of DCI. Not even exceptions are exempt from this rule.</p>
<p>In the previous parts' example, we have started the system operation (an entry point) in a code block at the end of the Context:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-comment">// System operation</span>
  Messages_hide();
}
</code></pre>
<p>There was a reason for the code block: It provides a place for error handling. It can now be easily modified to</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// System operation</span>
  <span class="hljs-keyword">await</span> Messages_hide();
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Error handling</span>
}
</code></pre>
<p>We can now assure that no errors will leak through the Context to another abstraction level. <code>try/catch</code> requires that we are using <code>async/await</code> for the RoleMethods, hence the added <code>await</code> keyword.</p>
<p>How the exceptions are handled varies, but in a web application, it's common to send errors to a logging service. It can be done at the top level, so it's not a strict requirement to catch and handle all errors, but as long as the errors are related to the Context it's a good practice, including being consistent about the approach that's taken.</p>
<h1 id="heading-the-final-rewrite-of-the-submitform-context">The final rewrite of the SubmitForm Context</h1>
<p>After making our SubmitForm Context use <code>async/await</code>, the final version looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { log } <span class="hljs-keyword">from</span> <span class="hljs-string">'./log'</span>;

<span class="hljs-comment">/** 
 * Submit a form and show error messages from the response.
 * @DCI-context
 */</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitForm</span>(<span class="hljs-params">e: SubmitEvent</span>) </span>{
  <span class="hljs-keyword">if</span> (!(e.target <span class="hljs-keyword">instanceof</span> HTMLFormElement)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No form found.'</span>);
  }

  <span class="hljs-comment">//#region Form Role /////</span>

  <span class="hljs-keyword">const</span> Form: {
    action: <span class="hljs-built_in">string</span>;
  } = e.target;

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Form_submit</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Role contract call (Form.action)</span>
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(Form.action, {
      method: <span class="hljs-string">'POST'</span>,
      body: <span class="hljs-keyword">new</span> FormData(Form <span class="hljs-keyword">as</span> HTMLFormElement)
    });

    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> error <span class="hljs-keyword">of</span> data.errors ?? []) {
      Messages_show(error); <span class="hljs-comment">// Role interaction</span>
    }
  }

  <span class="hljs-comment">//#endregion</span>

  <span class="hljs-comment">//#region Messages Role /////</span>

  <span class="hljs-keyword">const</span> Messages: Iterable&lt;{
    dataset: DOMStringMap;
    style: CSSStyleDeclaration;
  }&gt; = e.target.querySelectorAll&lt;HTMLElement&gt;(<span class="hljs-string">'[data-form-message]'</span>);

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_hide</span>(<span class="hljs-params"></span>) </span>{
    Messages__set(<span class="hljs-string">'none'</span>);
    <span class="hljs-keyword">await</span> Form_submit(); <span class="hljs-comment">// Role interaction</span>
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_show</span>(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) </span>{
    Messages__set(<span class="hljs-string">'unset'</span>, name);
  }

  <span class="hljs-comment">// Private RoleMethod (double underscore)</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages__set</span>(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = ''</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> Messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  }

  <span class="hljs-comment">//#endregion</span>

  <span class="hljs-keyword">try</span> {
    log(<span class="hljs-string">'Submit'</span>);
    e.preventDefault();
    <span class="hljs-keyword">await</span> Messages_hide(); <span class="hljs-comment">// System operation</span>
    log(<span class="hljs-string">'Done'</span>);
  } <span class="hljs-keyword">catch</span> (e) {
    log(e);
  }
}
</code></pre>
<p>(Code with demo is <a target="_blank" href="https://stackblitz.com/edit/dci-ts-part5?file=src%2Fmain.ts">available here</a> on Stackblitz.)</p>
<p>Some regions have been added for the Roles, which <a target="_blank" href="https://code.visualstudio.com/docs/editor/codebasics#_folding">can be folded in VS Code</a> for a better overview in large Contexts.</p>
<h1 id="heading-context-initialization-errors">Context initialization errors</h1>
<p>The astute reader may have noticed that we're throwing an exception at the beginning of the Context - outside the <code>try/catch</code> block - which may look contradictory to what's been said, but this initial error checking is more like a contract or a constructor than part of the Context behavior, and if that fails, it's nothing we can handle and must relinquish control, since the initiator didn't fulfill the conditions for initializing the Context.</p>
<h1 id="heading-reusable-roles">Reusable Roles?</h1>
<p>Before finishing this part, we have to tie up a loose end from part 3, where I mentioned that the language Haxe has something called <a target="_blank" href="https://haxe.org/manual/types-abstract.html">Abstract types</a>, an interesting and useful feature that looks similar to Roles, but since it's a separate type, similar to mixins and extension methods, etc, it can stand on its own.</p>
<p>This can lead to the question if Roles can be reusable as separate types, but Roles have a name and responsibilities that make sense only in a certain Context, so they cannot. The DCI FAQ <a target="_blank" href="https://fulloo.info/doku.php?id=why_can_t_i_reuse_a_role_across_multiple_contexts">has a question</a> dedicated to this topic, so we'll end this part with a quote from there:</p>
<blockquote>
<p>A Shape can move and draw in a graphical context; a Cowboy can move, draw and shoot in a Western movie context. Having a Role — named however you like — that can draw and shoot does not make it a candidate for reuse in another Context.</p>
</blockquote>
<p>As always, thank you for reading, just reach out if you have any questions!</p>
]]></content:encoded></item><item><title><![CDATA[The only two UX rules you need to know]]></title><description><![CDATA[What is this, the bold statement of the year? Only two rules needed in UX, an area filled with so many "must-read" books, expensive design schools and huge project checklists?
Call me bold, but I'd say yes. You have common sense within you. You have ...]]></description><link>https://blog.encodeart.dev/the-only-two-ux-rules-you-need-to-know</link><guid isPermaLink="true">https://blog.encodeart.dev/the-only-two-ux-rules-you-need-to-know</guid><category><![CDATA[UX]]></category><category><![CDATA[Design]]></category><category><![CDATA[user experience]]></category><category><![CDATA[UI]]></category><category><![CDATA[design principles]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Tue, 10 Jan 2023 19:16:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673367183898/f4c9d6b3-735f-4b0d-81e6-f274a4257193.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>What is this, the bold statement of the year? <strong>Only two rules</strong> needed in UX, an area filled with so many "must-read" books, expensive design schools and <a target="_blank" href="https://uxchecklist.github.io/">huge project checklists</a>?</p>
<p>Call me bold, but I'd say <strong>yes</strong>. You have common sense within you. You have experienced enough bad design to avoid disaster. And if you ever designed something bad, it was probably because you were stressed, or someone (your boss, a client, etc) made you do it.</p>
<p>"Sure, but still, only two rules?"</p>
<p><strong>Yes.</strong> Maybe you've already read them. They can be found in one of the "must-read" books. But it's understandable if you missed them, there are plenty of other must-read books to read.</p>
<h1 id="heading-ok-bring-them-on">OK, bring them on!</h1>
<p>All right, without further ado:</p>
<blockquote>
<p>Make sure that: (1) the user can figure out what to do, and (2) the user can tell what is going on.</p>
<p>-- Don Norman, "The Design of Everyday Things"</p>
</blockquote>
<p><strong>There, you've read them; now take a minute to ponder about them.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673365880614/27f13fb4-013f-4b7f-b5b1-af504de86735.jpeg" alt class="image--center mx-auto" /></p>
<p>That inner voice. Is it self-doubt, the endless marketing schemes, or silver bullet thinking that makes you reach for the latest advice, instead of simple, timeless advice?</p>
<p><em>"It's too simple,"</em> protests the inner voice from an unknown source. <em>"Everything should be made as simple as possible, but not simpler."</em> Now it's quoting Einstein and seems to gain a lot of confidence.</p>
<p>Before it convinces you, gently remind it that concrete advice and aphorisms are not on the same abstraction level, and one can hardly be used to dismiss the other.</p>
<p>Let's instead briefly look at the rules, one at a time, and see what insights we can gain.</p>
<h1 id="heading-1-make-sure-that-the-user-can-figure-out-what-to-do">1. Make sure that the user can figure out what to do</h1>
<p>Here's a screen from a well-known application where everything is laid out before us.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673367296946/4474603b-2f77-4b69-b488-aff5f27673f2.jpeg" alt class="image--center mx-auto" /></p>
<p>Isn't it great to have everything available at your fingertips? You only need to remember the function of all buttons and the layout, and you'll have no trouble doing exactly what you want. Right? No?</p>
<p>The inner voice seems to agree with us this time, it's overwhelming. So let us ask ourselves a key question: <strong>What is the conflict with rule #1?</strong></p>
<p>Of course, there are so many things on the screen that it's hard to figure out what to do. That was simple.</p>
<h2 id="heading-user-context-and-noun-verb-operations">User context and noun-verb operations</h2>
<p>That was indeed simple, because the <strong>user context</strong> is very important. If the user wants to crop an image, she should be able to figure out what to do <strong>in that context</strong>.</p>
<p>This goes hand in hand with a great piece of advice from <a target="_blank" href="https://books.google.se/books/about/The_Humane_Interface.html?id=D39vjmLfO3kC&amp;redir_esc=y">Jef Raskin</a>: <em>Prefer noun-verb construction instead of verb-noun.</em></p>
<p>In this case, cropping an image will then become <strong>image-crop</strong>, translating UX-wise to:</p>
<ol>
<li><p>First, select an <strong>image</strong></p>
</li>
<li><p>Then select the <strong>crop</strong> command and its parameters</p>
</li>
<li><p>Commit the operation.</p>
</li>
</ol>
<p>For every step, we can now reveal additional information to the user, to make her <strong>figure out what to do next</strong>. Compare that to the opposite, <strong>crop-image</strong>:</p>
<ol>
<li><p>First, select the <strong>crop</strong> command and its parameters <em>(must always be visible)</em></p>
</li>
<li><p>Then select an <strong>image</strong> <em>(not anything else!)</em></p>
</li>
<li><p>Commit the operation.</p>
</li>
</ol>
<p>Let's also say that the user is interrupted between steps 1 and 2 and comes back later, forgetting that a crop operation is ongoing. She clicks on a text paragraph, and an obscenely loud windows error sound reverberates through the room, making her drop the freshly brewed cup of tea over the desktop and her new $300 noise-canceling earbuds.</p>
<p>Way to go, verb... but hey, at least the UX didn't break rule #2! 😄</p>
<p>So many things follow from revealing things to the user based on noun context.</p>
<ul>
<li><p>...And by having clearly visible buttons/links/clickable areas for the most obvious actions to take at the time</p>
</li>
<li><p>And by pleasant typography</p>
</li>
<li><p>And a well-thought-out color scheme</p>
</li>
<li><p>And so on. You know this already.</p>
</li>
</ul>
<p>Just ask yourself at every step of designing a user interface, <strong>can the user figure out what to do?</strong></p>
<h1 id="heading-2-make-sure-the-user-can-tell-whats-going-on">2. Make sure the user can tell what's going on</h1>
<p>Interaction ahead! Now we're into real UX. Have you ever entered plenty of information into a website, clicked the submit button... and nothing happened. No wait, at the left bottom of the screen is our indicator of assurance:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1673372218123/75998344-c6ab-44e5-8605-532b62e05640.png" alt class="image--center mx-auto" /></p>
<p>Bless the browser for lightening the load of the designers, so they can hit the after-work early and look for the cute new girl at the office.</p>
<p>Isn't it ridiculous that the majority of websites can't at least provide feedback that their cheap hosting server is overloaded and will take up a bit of the user's precious time?</p>
<p>From now on we will respect the user more than that, right? But let's not break out the huge modal overlays already, let's see about the <strong>timing of notification</strong> first.</p>
<h2 id="heading-response-times">Response times</h2>
<p>Jakob Nielsen has a useful piece of advice for us here, from <a target="_blank" href="https://www.nngroup.com/articles/response-times-3-important-limits/">Response Times: The 3 Important Limits</a>:</p>
<ul>
<li><p><strong>0.1 second:</strong> The limit of feeling an instantaneous reaction from the system</p>
</li>
<li><p><strong>1.0 second:</strong> The limit of feeling that the user is operating directly on the data</p>
</li>
<li><p><strong>10 seconds:</strong> The limit of keeping the user's attention.</p>
</li>
</ul>
<p>This means that if the server is lightning fast with a response time of around 0.1 seconds, we don't need to provide any feedback. That will just be a distraction.</p>
<p>After perhaps 0.2 seconds, we can provide feedback, maybe a loading spinner inside the button, and/or a "submitting" message next to it.</p>
<p>After 6-7 seconds instead of 10 (compensating for the internet attention span), we'll add an extra notice that something is taking more time than it should, but please be patient, dear user. We're thrilled that you're submitting your information to us.</p>
<p>Now apply this example to anything that goes on in your design.</p>
<p>The same goes for mistakes. If the user makes a mistake, make sure it's obvious that a mistake has been made, but it's not the end of the world.</p>
<ul>
<li><p>Add helpful error messages next to the form field</p>
</li>
<li><p>Automatically scroll to the first error of a form, and place focus on the erroneous field</p>
</li>
<li><p>After nailing rule #1, let switching contexts happen without confusion</p>
</li>
<li><p>Etc!</p>
</li>
</ul>
<p>Whenever the user does something, or if something takes time, <strong>make sure the user can tell what's going on.</strong></p>
<h1 id="heading-were-at-the-end">We're at the end</h1>
<p>This text is deliberately brief, because of what was said in the beginning:</p>
<blockquote>
<p>You have common sense within you. You have experienced enough bad design to avoid disaster. And if you ever designed something bad, it was probably because you were stressed, or someone (your boss, a client, etc) made you do it.</p>
</blockquote>
<p>This may get philosophical, but the message is deeper than what an article or a must-read book can convey. When we're not blinded by stress, ambition, competition or money, we can build on timeless principles instead of trends. We do that by connecting to ourselves and others, with empathy, respect and insights from deeper knowledge, distilled into pieces of advice that could be used as rules in the right circumstances. UX is a fundamental part of being human, our body, senses, perception and life itself, everything is there for us to use and understand.</p>
<p>You can do that, and <em>by</em> doing that you're making the world better even by coding a website, but I invite you to expand the concept to all areas of your life. So make sure that: <strong>(1) your fellow humans can figure out what to do, and (2) your fellow humans can tell what is going on</strong>.</p>
]]></content:encoded></item><item><title><![CDATA[Typesafe i18n with SvelteKit]]></title><description><![CDATA[Creating multi-language support for a website is a good example of how "rolling your own" is easy at first, but gradually becomes more complicated until the realization that using a library would've been wiser. But then the challenge is to select an ...]]></description><link>https://blog.encodeart.dev/typesafe-i18n-with-sveltekit</link><guid isPermaLink="true">https://blog.encodeart.dev/typesafe-i18n-with-sveltekit</guid><category><![CDATA[i18n]]></category><category><![CDATA[Sveltekit]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[translation]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Mon, 09 Jan 2023 13:40:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673199495462/b4d8a3b2-b411-4524-98d7-384eb25efc84.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Creating multi-language support for a website is a good example of how "rolling your own" is easy at first, but gradually becomes more complicated until the realization that <em>using a library would've been wiser</em>. But then the challenge is to select an internationalization library that will play nicely with your framework of choice.</p>
<p>This article will show you how to connect two modern, well-thought-out parts of the web app toolchain. Let's start with the framework.</p>
<p>Most people seem to agree that <a target="_blank" href="https://kit.svelte.dev/">SvelteKit</a> has succeeded in creating a both powerful and joyful web development experience. And with the recent 1.0 release, there's no question that it's our framework of choice here.</p>
<p><strong>Edit:</strong> The code for this article is available on github <a target="_blank" href="https://github.com/ciscoheat/typesafe-i18n-with-sveltekit">here</a>.</p>
<h1 id="heading-creating-the-sveltekit-project-from-scratch">Creating the SvelteKit project from scratch</h1>
<p>The only prerequisite is <a target="_blank" href="https://nodejs.org/en/">Node.js</a>, which comes with its package manager <code>npm</code>. It's used here, but for a faster and overall nicer experience, install <a target="_blank" href="https://pnpm.io/">pnpm</a> and substitute all <code>npm</code> and <code>npx</code> commands with <code>pnpm</code> below. Open a terminal in your project folder and initialize a new SvelteKit project called "i18n" with:</p>
<pre><code class="lang-bash">npm create svelte@latest i18n
</code></pre>
<p>At the first two prompts, select <strong>Skeleton project</strong> and <strong>Typescript syntax</strong>, the rest is up to you. Then we're installing its dependencies:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> i18n
npm install
</code></pre>
<h2 id="heading-installing-the-i18n-library">Installing the i18n library</h2>
<p>Our i18n library of choice is called <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n">typesafe-i18n</a>, selected because it gets so many things right, from syntax to output size. We will also install a package called <code>npm-run-all</code> to be able to update the translation files in parallel with running the SvelteKit dev server:</p>
<pre><code class="lang-bash">npm install -D typesafe-i18n npm-run-all
</code></pre>
<p>After that, we will generate the configuration file for the translation:</p>
<pre><code class="lang-bash">npx typesafe-i18n --setup-auto
</code></pre>
<p>This will generate a <code>.typesafe-i18n.json</code> in the root project folder, that we should edit to specify an <code>outputPath</code> field. This is because we want to use SvelteKit's convenient <code>$lib</code> alias so we can refer to the translation anywhere in the project. Open <code>.typesafe-i18n.json</code> and add it:</p>
<p><strong>.typesafe-i18n.json</strong></p>
<pre><code class="lang-json">{
  ...
  <span class="hljs-attr">"outputPath"</span>: <span class="hljs-string">"./src/lib/i18n/"</span>
}
</code></pre>
<p>Finally, we will modify <code>package.json</code> to run <code>typesafe-i18n</code> and the <code>vite</code> dev server that SvelteKit uses at the same time:</p>
<p><strong>.package.json</strong></p>
<pre><code class="lang-json">{
  ...
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"npm-run-all --parallel vite typesafe-i18n"</span>,
    <span class="hljs-attr">"vite"</span>: <span class="hljs-string">"vite dev --open"</span>
    ...
  }
}
</code></pre>
<p>As you may have noticed further down in the file, <code>typesafe-i18n</code> has been added to <code>package.json</code> already.</p>
<p>Before starting SvelteKit, let's quickly go through the different levels of translation that <code>typesafe-i18n</code> provides.</p>
<h2 id="heading-lll-ll-and-l">LLL, LL and L</h2>
<p>There are three different levels to consider, the lowest being represented by the <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/runtime#i18nstring">LLL</a> object. It deals with a single translation string, transforming it to proper output. Knowing the translation string <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/runtime#syntax">syntax</a> is essential, but we won't use this object directly, since the next level is a more convenient way of outputting translated text in our SvelteKit app.</p>
<p>The <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/runtime#i18nobject">LL</a> object is much more suitable for the client, since it contains all strings for a specific <em>locale</em> (language settings with formatting rules) - we don't need to transfer all languages to the client, just the one we need.</p>
<p>Finally, the <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/runtime#i18n">L</a> object contains all available locales, making it more useful server-side.</p>
<p>We're going to use the <code>LL</code> object, which will be available to us in the auto-generated language files. So let's start up the dev server and see what happens:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Files have now been generated in <code>src/lib/i18n</code>, together with two example languages, <code>en</code> (the default) and <code>de</code>. Each language is represented by a string object that you can organize however you'd like, as a flat structure, or nested.</p>
<p>Also generated is a <code>src/lib/i18n/i18n-svelte.ts</code> file that exports a <code>LL</code> object. This is the one we're going to use, but first, given that we have two languages, how do we choose which one to load?</p>
<h2 id="heading-using-routing-for-language-detection">Using routing for language detection</h2>
<p>Some apps will have the current locale stored in user settings or a cookie, but we will take the route approach, having the locale in the first portion of the URL, like <code>/de/some/page</code>. (Changing to another way of detecting the locale is quite simple, as you will see later.)</p>
<p>SvelteKit's flexible routing system makes this quite easy. We're gonna make use of a route like this:</p>
<pre><code class="lang-plaintext">[lang]
</code></pre>
<p>Square brackets signify a <em>route parameter</em>, so we can use parts of the route dynamically. In this case, we want to access the first part of the route as the <code>lang</code> parameter. But we don't want it to be required, since the default language <code>en</code> shouldn't have its locale code prefixed. With <strong>double brackets it will be optional</strong>, meaning that routes like <code>/de/some/page</code> and <code>/some/page</code> will result in the same page being loaded.</p>
<p>Let's display a translation string, but first some cleanup: Delete the route files in <strong>src/routes</strong> so they won't conflict with the new route. Then create the route directory <strong>src/routes/[[lang]]</strong> (with double brackets) and a <code>+page.svelte</code> file inside it:</p>
<p><strong>src/routes/[[lang]]/+page.svelte</strong></p>
<pre><code class="lang-typescript">&lt;script lang=<span class="hljs-string">"ts"</span>&gt;
  <span class="hljs-keyword">import</span> { LL } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-svelte'</span>;
&lt;/script&gt;

&lt;h1&gt;{$LL.HI({name: <span class="hljs-string">"World"</span>})}&lt;/h1&gt;
</code></pre>
<p>If you open the project in a browser, you'll see a message blink by quickly. This is because the default locale loads automatically on the server, but not on the client.</p>
<p>We don't have a layout yet, but a <code>+layout.ts</code> file can be quite useful here, since (<a target="_blank" href="https://kit.svelte.dev/docs/load#universal-vs-server">from the SvelteKit docs</a>) <em>"+page.js and +layout.js files export universal load functions that run both on the server and in the browser".</em></p>
<p>With a top-level layout file, we make sure the current locale is always loaded for every page below it, both on the server and client. This is the place where we want to detect the locale based on the route. Let's start simple:</p>
<p><strong>src/routes/+layout.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { LayoutLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = (<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">return</span> event.data;
}) satisfies LayoutLoad;
</code></pre>
<p>This minimal file just passes on the event data from a future <code>+layout.server.ts</code> file. And the <code>event</code> parameter contains a <code>params</code> property with the <code>lang</code> route that we specified earlier.</p>
<h3 id="heading-locale-detection">Locale detection</h3>
<p>Instead of using <code>event.params.lang</code> directly to set the locale, a <code>detectLocale</code> function is available from the generated i18n files. It's preferred to use since we can supply it with a <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/detectors#typesafe-i18n-detectors">matcher function</a>, which will return a strongly typed locale (the default if the matcher fails), so we don't have to check for incorrect values. Let's use it:</p>
<p><strong>src/routes/+layout.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { LayoutLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
<span class="hljs-keyword">import</span> { detectLocale } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-util'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = (<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">const</span> locale = detectLocale(<span class="hljs-function">() =&gt;</span> [event.params.lang ?? <span class="hljs-string">''</span>]);
  <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Load and set locale</span>
  <span class="hljs-keyword">return</span> event.data;
}) satisfies LayoutLoad;
</code></pre>
<p>Now we have the locale, but we must load it and set it before it can be displayed on the client. Fortunately, the generated files have us covered. First, we'll use the <code>loadLocaleAsync</code> utility function, then we use a Svelte-specific <code>setLocale</code> to populate the <code>LL</code> object we used earlier. Here's the completed layout file:</p>
<p><strong>src/routes/+layout.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { LayoutLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
<span class="hljs-keyword">import</span> { loadLocaleAsync } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-util.async'</span>;
<span class="hljs-keyword">import</span> { setLocale } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-svelte'</span>;
<span class="hljs-keyword">import</span> { detectLocale } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-util'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = (<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-comment">// Detect the locale</span>
  <span class="hljs-keyword">const</span> locale = detectLocale(<span class="hljs-function">() =&gt;</span> [event.params.lang ?? <span class="hljs-string">''</span>]);
  <span class="hljs-comment">// Load it</span>
  <span class="hljs-keyword">await</span> loadLocaleAsync(locale);
  <span class="hljs-comment">// Set it</span>
  setLocale(locale);

  <span class="hljs-keyword">return</span> event.data;
}) satisfies LayoutLoad;
</code></pre>
<p>Now open the front page, and you should see the translation string. Change the url to <code>/de</code> and the german version will load.</p>
<h2 id="heading-adding-translation-strings-and-another-page">Adding translation strings and another page</h2>
<p>Adding more translation strings and routes is as easy as adding to the <code>src/lib/i18n/en/index.ts</code> file and its <code>de</code> counterpart, following the <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n/tree/main/packages/runtime#syntax">syntax</a> of typesafe-i18n. It makes things like plural rather simple, which could be a pain to implement by yourself:</p>
<p><strong>src/lib/i18n/en/index.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> en: BaseTranslation = {
  ...
  APPLES: <span class="hljs-string">'{apples:number} apple{{s}}'</span>,
};
</code></pre>
<p>We'll use this soon, but first, we need to translate it into german. When opening the corresponding <code>src/lib/i18n/de/index.ts</code> file, you'll be notified that the <code>APPLES</code> property is missing, which is a nice reminder to get. But we have another problem, the singular and plural of apple are spelled differently in german. Well, no problem:</p>
<p><strong>src/lib/i18n/de/index.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> de: Translation = {
  ...
  APPLES: <span class="hljs-string">'{apples} {{Apfel|Äpfel}}'</span>,
};
</code></pre>
<p>Note that unlike the <code>en</code> file, we don't have to specify the type of the <code>{apples}</code> argument. Now let's create the new route by adding two folders and files:</p>
<p><strong>src/routes[[lang]]/apples/[amount]/+page.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PageLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = (<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-keyword">return</span> { 
    apples: <span class="hljs-built_in">parseInt</span>(event.params.amount) 
  };
}) satisfies PageLoad;
</code></pre>
<p><strong>src/routes[[lang]]/apples/[amount]/+page.svelte</strong></p>
<pre><code class="lang-typescript">&lt;script lang=<span class="hljs-string">"ts"</span>&gt;
  <span class="hljs-keyword">import</span> { LL } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-svelte'</span>;
  <span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { PageData } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> data: PageData;
&lt;/script&gt;

&lt;h1&gt;{$LL.APPLES(data)}&lt;/h1&gt;
</code></pre>
<p>Try it out by browsing to <code>/de/apples/2</code> for example. Convenient, isn't it?</p>
<h2 id="heading-linking-between-pages">Linking between pages</h2>
<p>We have pretty much covered the basics now, but one thing that's easy to overlook is how to link between pages while keeping the locale intact. The solution is to translate the links as well, prepending the locale before the actual route:</p>
<p><strong>src/lib/i18n/en/index.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> en: BaseTranslation = {
  ...
  LINK: <span class="hljs-string">'{0}'</span>, <span class="hljs-comment">// No prefix needed</span>
};
</code></pre>
<p><strong>src/lib/i18n/de/index.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> de: Translation = {
  ...
  LINK: <span class="hljs-string">'/de{0}'</span>, <span class="hljs-comment">// Prepending the locale</span>
};
</code></pre>
<p>Now we can link back to the start page:</p>
<p><strong>src/routes/[[lang]]/apples/[amount]/+page.svelte</strong></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{$LL.APPLES(data)}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{$LL.LINK(</span>'/')}&gt;</span>Back to start<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</code></pre>
<p>And it should be no problem for you to translate the "Back to start" phrase now if you'd like!</p>
<h2 id="heading-matching-only-the-defined-languages">Matching only the defined languages</h2>
<p>We have a slight problem though: We can enter anything as the "lang" route parameter, and the start page will be loaded in english. We'd like any non-existing language to return a 404 instead.</p>
<p>This can be done using a <a target="_blank" href="https://kit.svelte.dev/docs/advanced-routing#matching">route matcher</a> for matching only the languages we have a translation for. We're using the locale data from <code>i18n-util</code> to exclude the default locale and check if the parameter matches one of the others:</p>
<p><strong>src/params/langCode.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ParamMatcher } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sveltejs/kit'</span>;
<span class="hljs-keyword">import</span> { baseLocale, locales } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-util'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Locales } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-types'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> match: ParamMatcher = <span class="hljs-function">(<span class="hljs-params">param</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> param !== baseLocale &amp;&amp; locales.includes(param <span class="hljs-keyword">as</span> Locales);
};
</code></pre>
<p>The name of this file (without extension), <code>langCode</code>, is what we should use in the route name. So rename the <code>[[lang]]</code> route directory to <code>[[lang=langCode]]</code> and try to browse a non-existing locale. A 404 should be the result. The same could be done with the <code>[amount]</code> route parameter, using a matcher that checks for an integer.</p>
<h2 id="heading-one-final-challenge-using-a-cookie-instead-of-routes">One final challenge: Using a cookie instead of routes</h2>
<p>To keep the routes clean, you may want to store the selected locale in a cookie instead. This isn't too hard to do, but it requires a change; the <code>src/routes/+layout.ts</code> file doesn't have direct access to cookies, so we need to use an additional <code>+layout.server.ts</code> file, with some minor changes to <code>+layout.ts</code>:</p>
<p><strong>src/routes/+layout.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { LayoutLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
<span class="hljs-keyword">import</span> { loadLocaleAsync } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-util.async'</span>;
<span class="hljs-keyword">import</span> { setLocale } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-svelte'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = (<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-comment">// Locale now comes from the server instead of the route</span>
  <span class="hljs-keyword">const</span> locale = event.data.locale;
  <span class="hljs-comment">// But we load and set it as before</span>
  <span class="hljs-keyword">await</span> loadLocaleAsync(locale);
  setLocale(locale);

  <span class="hljs-keyword">return</span> event.data;
}) satisfies LayoutLoad;
</code></pre>
<p><strong>src/routes/+layout.server.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { LayoutServerLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
<span class="hljs-keyword">import</span> { detectLocale } <span class="hljs-keyword">from</span> <span class="hljs-string">'$lib/i18n/i18n-util'</span>;
<span class="hljs-keyword">import</span> { redirect } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sveltejs/kit'</span>;

<span class="hljs-keyword">const</span> langParam = <span class="hljs-string">'lang'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load = (<span class="hljs-keyword">async</span> (event) =&gt; {
  <span class="hljs-comment">// Using a GET var "lang" to change locale</span>
  <span class="hljs-keyword">const</span> newLocale = event.url.searchParams.get(langParam);
  <span class="hljs-keyword">if</span> (newLocale) {
    event.cookies.set(langParam, newLocale, { path: <span class="hljs-string">'/'</span> });
    event.url.searchParams.delete(langParam);
    <span class="hljs-comment">// Redirect to remove the GET var    </span>
    <span class="hljs-keyword">throw</span> redirect(<span class="hljs-number">303</span>, event.url.toString());
  }

  <span class="hljs-comment">// Get the locale from the cookie</span>
  <span class="hljs-keyword">const</span> locale = detectLocale(<span class="hljs-function">() =&gt;</span> [event.cookies.get(langParam) ?? <span class="hljs-string">''</span>]);
  <span class="hljs-keyword">return</span> { locale };
}) satisfies LayoutServerLoad;
</code></pre>
<p>Even cookie handling is trivial with SvelteKit! We're now passing on the <code>locale</code> from the server to the universal <code>+layout.ts</code> file, which will use <code>event.data.locale</code> instead of <code>event.params.lang</code> as before. And everything is strongly typed, again thanks to SvelteKit.</p>
<p>Test this out by browsing to:</p>
<pre><code class="lang-typescript">/apples/<span class="hljs-number">1</span>?lang=de
</code></pre>
<p>This also means that we don't need the <code>$LINK</code> route translation anymore, so it can be removed.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope this article has helped show that i18n is quite manageable once you pass the "setup hurdle". It's great to see that both typesafe-i18n and SvelteKit are leveraging Typescript to give useful hints about route parameters, missing translation strings, etc.</p>
<p>To dive deeper into typesafe-i18n, check out its <a target="_blank" href="https://github.com/ivanhofer/typesafe-i18n#table-of-contents">Table of Contents</a>, the SvelteKit documentation is <a target="_blank" href="https://kit.svelte.dev/docs">available here</a>, and let me know if you have any questions or comments. Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[DCI tutorial for TypeScript: Part 4]]></title><description><![CDATA[Ready for more DCI? If you're new, check out the other parts first.
We talked a lot about Roles in the previous part, concluding that Roles are a quite natural way of expressing ourselves in code. This has led to many attempts at adding Roles to lang...]]></description><link>https://blog.encodeart.dev/dci-tutorial-for-typescript-part-4</link><guid isPermaLink="true">https://blog.encodeart.dev/dci-tutorial-for-typescript-part-4</guid><category><![CDATA[dci]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Object Oriented Programming]]></category><category><![CDATA[System Architecture]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Sat, 05 Nov 2022 19:06:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667659043142/Z5Gu2TuBg.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Ready for more DCI? If you're new, check out <a target="_blank" href="https://blog.encodeart.dev/series/dci-typescript-tutorial">the other parts</a> first.</p>
<p>We talked a lot about Roles in the previous part, concluding that Roles are a quite natural way of expressing ourselves in code. This has led to many attempts at adding Roles to languages without introducing additional syntax. This has also been a huge source of misunderstanding regarding DCI, so let's clear this one once and for all.</p>
<h1 id="heading-dci-roles-cannot-be-wrappers">DCI Roles cannot be wrappers</h1>
<p>Who needs the underscore RoleMethod convention? Let's use objects instead for the example in Part 3:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Messages = (<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">
  Messages: {
    [<span class="hljs-built_in">Symbol</span>.iterator]: () =&gt; IterableIterator&lt;HTMLElement&gt;;
  }
</span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span>(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = ''</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> Messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  }
  <span class="hljs-keyword">return</span> {
    show(name: <span class="hljs-built_in">string</span>) {
      set(<span class="hljs-string">'unset'</span>, name);
    },
    hide() {
      set(<span class="hljs-string">'none'</span>);
    },
  };
})(e.target.querySelectorAll(<span class="hljs-string">'[data-form-message]'</span>));

<span class="hljs-comment">// Now we're free to use the Role with normal method syntax:</span>
Messages.hide()
</code></pre>
<p>This seems to be working but unfortunately isn't. This has been <em>the</em> number one obstacle when trying to understand DCI: Failing to understand that <strong>you cannot wrap a Role in, or substitute it for another object</strong>. By doing that you are violating object identity, which could work in many cases, but not all, and that kind of uncertainty is not what we want in software.</p>
<p>The problem is that <em>any</em> other part of the program, and that can be a huge part considering the number of external dependencies of a project, doing an identity check (<code>==</code> or <code>===</code>) on the Messages Role will now fail this check, even though it was supposed not to.</p>
<p><a target="_blank" href="https://fulloo.info/doku.php?id=why_isn_t_it_dci_if_you_use_a_wrapper_object_to_represent_the_role">This FAQ entry</a> explains the whole thing in more detail, and I've made an <a target="_blank" href="https://stackblitz.com/edit/dci-ts-wrapper-bug?file=src%2Fmain.ts">example on Stackblitz</a> that shows how these bugs can appear, even in a strongly typed language like TypeScript.</p>
<p>Now that we have gotten this out of the way, let's move on to more interesting things.</p>
<h1 id="heading-adding-actual-interaction">Adding actual interaction</h1>
<p>A question was asked at the end of Part 3: <em>If there is only one Role, can there be interaction?</em></p>
<p>Interaction presupposes more than one part, so the answer seems to be no, and this is also the case with DCI. Programs with minimal object interaction are probably just as readable without Roles, data transforming with functional programming comes to mind here as well. But it's worth considering that something simple can grow more complicated, and the more variables introduced, the greater the chance that some interaction happens between them.</p>
<p>Looking at our <code>SubmitForm</code> Context again, is it too simple for Roles?</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @DCI-context</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitForm</span>(<span class="hljs-params">e: SubmitEvent</span>) </span>{
  e.preventDefault();
  <span class="hljs-keyword">if</span> (!(e.target <span class="hljs-keyword">instanceof</span> HTMLFormElement)) 
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No form found.'</span>);

  <span class="hljs-comment">// Role</span>
  <span class="hljs-keyword">const</span> Messages : Iterable&lt;HTMLElement&gt; = 
    e.target.querySelectorAll(<span class="hljs-string">'[data-form-message]'</span>);

  <span class="hljs-comment">// RoleMethods</span>
  <span class="hljs-keyword">const</span> Messages__set = <span class="hljs-function">(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = <span class="hljs-string">''</span></span>) =&gt;</span> {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> Messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  };

  <span class="hljs-keyword">const</span> Messages_show = <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) =&gt;</span> Messages__set(<span class="hljs-string">'unset'</span>, name);

  <span class="hljs-keyword">const</span> Messages_hide = <span class="hljs-function">() =&gt;</span> Messages__set(<span class="hljs-string">'none'</span>);

  {
    <span class="hljs-comment">// System operation</span>
    <span class="hljs-keyword">const</span> form: HTMLFormElement = e.target <span class="hljs-keyword">as</span> HTMLFormElement;

    Messages_hide();
    fetch(form.action, { method: <span class="hljs-string">'POST'</span> })
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
      .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> data.errors?.forEach(Messages_show));
  }
}
</code></pre>
<p>Well, something seems to interacting in the system operation:</p>
<pre><code class="lang-typescript">{
  <span class="hljs-comment">// System operation</span>

  <span class="hljs-comment">// We're creating a new variable here:</span>
  <span class="hljs-keyword">const</span> form: HTMLFormElement = e.target <span class="hljs-keyword">as</span> HTMLFormElement;

  <span class="hljs-comment">// This is the actual system operation - a RoleMethod call</span>
  Messages_hide();

  <span class="hljs-comment">// Form submit behavior</span>
  fetch(form.action, { method: <span class="hljs-string">'POST'</span> })
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
    <span class="hljs-comment">// Interaction with Messages.show</span>
    .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> data.errors?.forEach(Messages_show));
}
</code></pre>
<p>I'd say we have created a Role without really noticing:</p>
<ul>
<li><p>An identifier (<code>form</code>) is created and assigned</p>
</li>
<li><p>It has behavior (submit the form)</p>
</li>
<li><p>It interacts with the <code>Messages</code> role.</p>
</li>
</ul>
<p>This means that we in principle have two Roles, and therefore interaction! Let's modify the Context to use a <code>Form</code> Role instead:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @DCI-context</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitForm</span>(<span class="hljs-params">e: SubmitEvent</span>) </span>{
  e.preventDefault();
  <span class="hljs-keyword">if</span> (!(e.target <span class="hljs-keyword">instanceof</span> HTMLFormElement)) 
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No form found.'</span>);

  <span class="hljs-comment">// Role</span>
  <span class="hljs-keyword">const</span> Form: {
    action: <span class="hljs-built_in">string</span>;
  } = e.target;

  <span class="hljs-comment">// RoleMethods</span>
  <span class="hljs-keyword">const</span> Form_submit = <span class="hljs-function">() =&gt;</span> {
    Messages_hide();
    fetch(Form.action, { method: <span class="hljs-string">'POST'</span> })
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
      .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> data.errors?.forEach(Messages_show));
  };

  <span class="hljs-comment">// Messages role omitted, it's unchanged.</span>
  <span class="hljs-comment">// ...</span>

  {
    <span class="hljs-comment">// System operation</span>
    Form_submit();
  }
}
</code></pre>
<p>It's now more apparent what's going on. The Role contract is literal, so we can immediately see that the object playing the <code>Form</code> Role only needs an <code>action</code> property to do that. And remember that the <code>action</code> property is protected from access by the rest of the Context. If we want to access it, we have to be explicit and use the Form's public RoleMethods.</p>
<h1 id="heading-distributing-the-interaction">Distributing the interaction</h1>
<p>What we have done now, however, is to create an imperative, all-knowing Role that limits the DCI idea of describing <em>a network of interacting objects</em>. Putting the interactions in one place like this is a step back to procedural programming (like the previous system operation, though perhaps it was simple enough to get away with it).</p>
<p>Older languages like Fortran and Pascal worked in sequence and told in detail to the computer what to execute. This is not what we want, since objects in the network are non-static entities. By strictly organizing them, telling them what to do and handling their return values, we prevent interaction and communication. This <a target="_blank" href="https://fulloo.info/doku.php?id=what_is_the_advantage_of_distributing_the_interaction_algorithm_in_the_rolemethods_as_suggested_by_dci_instead_of_centralizing_it_in_a_context_method">FAQ entry</a> explains in more depth.</p>
<p>Instead of limiting communication, let's think of how our objects could interact and communicate more dynamically. Here is a breakdown of the code in its current, procedure-like fashion:</p>
<ol>
<li><p>Tell the messages to hide</p>
</li>
<li><p>Fetch the form action and return the errors</p>
</li>
<li><p>Tell the messages to show the errors</p>
</li>
</ol>
<p>Let's instead <em>distribute</em> this algorithm in an object-oriented fashion:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667667538814/MnZOn3Sif.png" alt="DCI distributed algorithm@2x.png" class="image--center mx-auto" /></p>
<p>Now the RolePlayers are communicating in a message-passing manner, which is not only more connected to mental modeling and DCI concepts, it is also more genuine:</p>
<ol>
<li><p><strong>User:</strong> Messages, can you hide?</p>
</li>
<li><p><strong>Messages:</strong> (Hiding) Form, can you submit?</p>
</li>
<li><p><strong>Form:</strong> (Submitting) Messages, can you show these errors?</p>
</li>
<li><p><strong>Messages:</strong> Hey User, here are some error messages.</p>
</li>
</ol>
<p>Now we're asking, not telling. By asking, we get rid of the assumption that the object can always do what we want (a more reasonable error handling, in other words). We will talk more about error handling in the next part.</p>
<p>With this, I think we're ready to update the Context (<a target="_blank" href="https://stackblitz.com/edit/dci-ts-part4?file=src%2Fmain.ts">code on Stackblitz</a>).</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @DCI-context</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitForm</span>(<span class="hljs-params">e: SubmitEvent</span>) </span>{
  e.preventDefault();
  <span class="hljs-keyword">if</span> (!(e.target <span class="hljs-keyword">instanceof</span> HTMLFormElement)) 
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No form found.'</span>);

  <span class="hljs-comment">// Role</span>
  <span class="hljs-keyword">const</span> Form: {
    action: <span class="hljs-built_in">string</span>;
  } = e.target;

  <span class="hljs-comment">// RoleMethods</span>
  <span class="hljs-keyword">const</span> Form_submit = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(Form.action, {
      method: <span class="hljs-string">'POST'</span>,
      body: <span class="hljs-keyword">new</span> FormData(e.target <span class="hljs-keyword">as</span> HTMLFormElement),
    });
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

    <span class="hljs-comment">// Role communication/interaction</span>
    data.errors?.forEach(Messages_show);
  };

  <span class="hljs-comment">// Role</span>
  <span class="hljs-keyword">const</span> Messages: Iterable&lt;HTMLElement&gt; = 
    e.target.querySelectorAll(<span class="hljs-string">'[data-form-message]'</span>);

  <span class="hljs-comment">// RoleMethods</span>
  <span class="hljs-keyword">const</span> Messages_hide = <span class="hljs-function">() =&gt;</span> {
    Messages__set(<span class="hljs-string">'none'</span>);
    <span class="hljs-comment">// Role communication/interaction</span>
    Form_submit();
  };

  <span class="hljs-keyword">const</span> Messages_show = <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    Messages__set(<span class="hljs-string">'unset'</span>, name);
  };

  <span class="hljs-keyword">const</span> Messages__set = <span class="hljs-function">(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = <span class="hljs-string">''</span></span>) =&gt;</span> {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> Messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  };

  {
    <span class="hljs-comment">// System operation</span>
    Messages_hide();
  }
}
</code></pre>
<p>The interactions now follow our mental model very closely. For further analysis, tracing a code path through the Context for this specific Interaction can be done with a sequence diagram:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667668588903/IUpcqo4AF.png" alt="DCI sequence diagram@2x.png" class="image--center mx-auto" /></p>
<h1 id="heading-rewiring-the-context">"Rewiring" the Context</h1>
<p>The flexibility of a distributed algorithm like this will be more evident as the system grows. By avoiding return values (though they are not forbidden), the parts of the system become more modular, and as functionality changes, we can more easily "rewire" the interactions similar to patching an audio effect rack, where a cable can send messages to another part of the rack.</p>
<p>Rewiring is much harder when we rely on return values to tell the system what to do next. It creates a coupling to the return value type, takes us away from the idea of mental modeling, and invokes the imperative programming style that isn't very object-oriented.</p>
<h2 id="heading-context-visualization">Context visualization</h2>
<p>Looking at physical cables we can see how messages could travel, so it can help visualize the system until it gets too messy. Being able to pass messages without physical cables avoids the mess, but on the other hand, makes debugging less practical! Previously we've had trouble determining what part of the system to diagram since the behavior was spread out through multiple abstraction boundaries. The boundary of a Context though, as a distinct unit of system behavior, makes it appropriate for visualization tools to display its interactions, as I've experimented with earlier:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667674391777/E90jdT45d.png" alt="DCI Context visualization.png" class="image--center mx-auto" /></p>
<p>The Roles have different colors, the inner circle contains RoleMethods, and behind them, in the outermost layer, are the Role contract fields. I can put an interactive diagram online if there is enough interest.</p>
<h1 id="heading-in-the-next-part">In the next part</h1>
<p>In the next part of this series, we will look at Context error handling with the least amount of surprise. <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-5">See you there</a>!</p>
]]></content:encoded></item><item><title><![CDATA[DCI tutorial for TypeScript: Part 3]]></title><description><![CDATA[Welcome to Part 3 of the DCI tutorial! If you just arrived, check out Part 1 and Part 2 first.
You have probably used Roles many times before
Roles are the central locus of a DCI Context, and chances are that you unknowingly have used them before. Le...]]></description><link>https://blog.encodeart.dev/dci-tutorial-for-typescript-part-3</link><guid isPermaLink="true">https://blog.encodeart.dev/dci-tutorial-for-typescript-part-3</guid><category><![CDATA[dci]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[roles]]></category><category><![CDATA[System Architecture]]></category><category><![CDATA[System Design]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Sun, 30 Oct 2022 22:54:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667170454533/fvhikIne9.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to Part 3 of the DCI tutorial! If you just arrived, check out <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-1">Part 1</a> and <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-2">Part 2</a> first.</p>
<h1 id="heading-you-have-probably-used-roles-many-times-before">You have probably used Roles many times before</h1>
<p><strong>Roles</strong> are the central locus of a DCI Context, and chances are that you unknowingly have used them before. Let's take a look at a more realistic example: An AJAX form event handler that will display validation messages, but they should all be hidden when submitting the form and displayed when the response arrives.</p>
<p>The initial code could be something like this:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">submitForm</span>(<span class="hljs-params">e: SubmitEvent</span>) </span>{
  e.preventDefault();
  <span class="hljs-keyword">if</span> (!(e.target <span class="hljs-keyword">instanceof</span> HTMLFormElement)) 
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No form found.'</span>);

  <span class="hljs-keyword">const</span> messages =
    e.target.querySelectorAll(<span class="hljs-string">'[data-form-message]'</span>) 
      <span class="hljs-keyword">as</span> NodeListOf&lt;HTMLElement&gt;;

  <span class="hljs-comment">// ...to be continued</span>
</code></pre>
<p>And from there, you want to show and hide the messages, so you create functions for that:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> hideMessages = <span class="hljs-function">() =&gt;</span> {
    messages.forEach(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> (m.style.display = <span class="hljs-string">'none'</span>));
  };

  <span class="hljs-keyword">const</span> showMessage = <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
    messages.forEach(<span class="hljs-function">(<span class="hljs-params">m</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (m.dataset.formMessage == name) m.style.display = <span class="hljs-string">'unset'</span>;
    });
  };
</code></pre>
<p>But these functions have too much in common not to be factorized, so you do that with a <code>setMessage</code> function:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> setMessage = <span class="hljs-function">(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = <span class="hljs-string">''</span></span>) =&gt;</span> {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  };

  <span class="hljs-keyword">const</span> showMessage = <span class="hljs-function">(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) =&gt;</span> setMessage(<span class="hljs-string">'unset'</span>, name);
  <span class="hljs-keyword">const</span> hideMessages = <span class="hljs-function">() =&gt;</span> setMessage(<span class="hljs-string">'none'</span>);
</code></pre>
<p>And finally, a bit of simplified code for the actual form submission:</p>
<pre><code class="lang-typescript">  <span class="hljs-keyword">const</span> form: HTMLFormElement = e.target <span class="hljs-keyword">as</span> HTMLFormElement;

  hideMessages();
  fetch(form.action, { method: <span class="hljs-string">'POST'</span> })
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
    .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> data.errors?.forEach(showMessage));
}
</code></pre>
<p>Problem solved, for now. Let's say that the next feature request is to automatically scroll to the topmost error message. So you'll add another function or two, copy/paste a helper function from Stack Overflow, and everything's fine again for a while. And the next is to scratch the itch of <code>data-form-message</code> being hardcoded, so options are introduced. And so on...</p>
<p>Adding functionality in this manner makes our mini-system quickly start to fragment. Variables will be spread throughout, with a multitude of functions that are used from anywhere within it. Let's look at the initial ones:</p>
<ul>
<li><code>setMessage</code>, <code>showMessage</code>, <code>hideMessages</code></li>
</ul>
<p>Both namewise and by their usage, they're related to the <code>messages</code> variable that we created earlier, which is an identifier for an array of HTML elements. So maybe they should be grouped like methods on an object, pointing us to a class as a possible solution?</p>
<h2 id="heading-classes-to-the-rescue">Classes to the rescue?</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> FormMessages {
  <span class="hljs-keyword">readonly</span> messages: NodeListOf&lt;HTMLElement&gt;;

  <span class="hljs-keyword">constructor</span>(<span class="hljs-params">form : HTMLFormElement</span>) {
    <span class="hljs-built_in">this</span>.messages = form.querySelectorAll(<span class="hljs-string">'[data-form-message]'</span>);
  }

  set(display: <span class="hljs-built_in">string</span>, name = <span class="hljs-string">''</span>) {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> <span class="hljs-built_in">this</span>.messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  }

  show(name: <span class="hljs-built_in">string</span>) {
    <span class="hljs-built_in">this</span>.set(<span class="hljs-string">'unset'</span>, name);
  }

  hide() {
    <span class="hljs-built_in">this</span>.set(<span class="hljs-string">'none'</span>);
  }
}
</code></pre>
<p>One problem with classes, with their static set of functionality and relationships, is that they are falling into what the authors of DCI call "<a target="_blank" href="https://groups.google.com/g/object-composition/c/umY_w1rXBEw/m/hyAF-jPgFn4J">Restricted OO</a>". By introducing a separate class, we'll be confronted by <code>FormMessages</code> instead of a literal type, which means we have to look up and grasp the intricacies of that class before being able to fully understand what our form submit function does. Check the link for more details, it's quite a read.</p>
<h2 id="heading-objects-to-the-rescue-then">Objects to the rescue, then?</h2>
<p>We're transpiling to Javascript, which can create objects in other ways than classes, so can we circumvent the class issue? <em>(no, not the real-world issue!)</em></p>
<p>Unfortunately not; the problem with objects, instantiated by classes or not, is that they are encapsulating three things: <strong>state</strong>, <strong>behavior</strong> and <strong>identity</strong>, but in our case, we only need one.</p>
<ul>
<li><p><strong>State</strong> is already contained in a <code>NodeListOf&lt;HTMLElement&gt;</code></p>
</li>
<li><p>That list has <strong>identity</strong> as well, which can be tested with an equality operator, and could lead to very subtle bugs if identity is violated by wrappers</p>
</li>
<li><p>All we need is the <strong>behavior</strong> we defined earlier: <code>setMessage</code>, <code>showMessage</code>, <code>hideMessages</code>.</p>
</li>
</ul>
<p>We are clearly missing a language feature that handles our case, and as you probably have figured out, that would be a <strong>DCI Role</strong>.</p>
<h2 id="heading-roles-to-the-rescue">Roles to the rescue!</h2>
<p>If Roles would be a built-in feature of Typescript, our <code>Messages</code> Role could perhaps look like this:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// ...inside a Context</span>

role Messages {
  <span class="hljs-comment">// Role contract</span>
  Iterable&lt;HTMLElement&gt;;

  <span class="hljs-comment">// RoleMethods</span>
  show(name: <span class="hljs-built_in">string</span>) {
    set(<span class="hljs-string">'unset'</span>, name);
  }

  hide() {
    set(<span class="hljs-string">'none'</span>);
  }

  <span class="hljs-keyword">private</span> set(display: <span class="hljs-built_in">string</span>, name: <span class="hljs-built_in">string</span>) {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> self) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  }
}
</code></pre>
<p>...but lacking this, we have to make compromises like the lamented underscore convention. Our faux-Role in Typescript looks like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> Messages : Iterable&lt;HTMLElement&gt; = 
  e.target.querySelectorAll(messageSelector);

<span class="hljs-comment">// RoleMethods</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_show</span>(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) </span>{
  Messages_set(<span class="hljs-string">'unset'</span>, name);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_hide</span>(<span class="hljs-params"></span>) </span>{
  Messages_set(<span class="hljs-string">'none'</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_set</span>(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = ''</span>) </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> Messages) {
    <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
    msg.style.display = display;
  }
}
</code></pre>
<p>This is the reason the <a target="_blank" href="https://github.com/ciscoheat/eslint-plugin-dci-lint">ESLint DCI library</a> was created - to ensure the closest possible emulation of Roles.</p>
<h2 id="heading-rewriting-the-form-validation">Rewriting the form validation</h2>
<p>This part is getting long enough, so let's wrap it up by rewriting the form submit function as a DCI Context:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// @DCI-context</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SubmitForm</span>(<span class="hljs-params">e: SubmitEvent</span>) </span>{
  e.preventDefault();
  <span class="hljs-keyword">if</span> (!(e.target <span class="hljs-keyword">instanceof</span> HTMLFormElement)) 
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'No form found.'</span>);

  <span class="hljs-comment">// Role (Data)</span>
  <span class="hljs-keyword">const</span> Messages: Iterable&lt;HTMLElement&gt; = 
    e.target.querySelectorAll(<span class="hljs-string">'[data-form-message'</span>]);

  <span class="hljs-comment">// RoleMethods (System behavior)</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_show</span>(<span class="hljs-params">name: <span class="hljs-built_in">string</span></span>) </span>{
    Messages__set(<span class="hljs-string">'unset'</span>, name);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages_hide</span>(<span class="hljs-params"></span>) </span>{
    Messages__set(<span class="hljs-string">'none'</span>);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Messages__set</span>(<span class="hljs-params">display: <span class="hljs-built_in">string</span>, name = ''</span>) </span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> msg <span class="hljs-keyword">of</span> Messages) {
      <span class="hljs-keyword">if</span> (name &amp;&amp; msg.dataset.formMessage != name) <span class="hljs-keyword">continue</span>;
      msg.style.display = display;
    }
  }

  {
    <span class="hljs-comment">// System operation (Interaction)</span>
    <span class="hljs-keyword">const</span> form: HTMLFormElement = e.target <span class="hljs-keyword">as</span> HTMLFormElement;

    Messages_hide();
    fetch(form.action, { method: <span class="hljs-string">'POST'</span> })
      .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
      .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> data.errors.forEach(Messages_show));
  }
}
</code></pre>
<p>The code is <a target="_blank" href="https://stackblitz.com/edit/dci-ts-part3?file=src%2Fmain.ts&amp;terminal=dev">available on Stackblitz</a> as usual.</p>
<p>Connecting back to the start - <strong>"You have probably used Roles before"</strong> - we've been doing it all the time, creating an identifier and functions with related behavior to it, but lacking the language feature it's very hard to see it - and honestly very hard to live without when you've grasped the concept of Roles.</p>
<p>It's very rare to see Role-like concepts in programming languages today, but the closest I've seen is <a target="_blank" href="https://haxe.org">Haxe</a> with its <a target="_blank" href="https://haxe.org/manual/types-abstract.html">Abstract types</a>, though that cannot be a real DCI Role because of a reason that will be explained later. (On the other hand, Haxe has other features that <a target="_blank" href="https://github.com/ciscoheat/haxedci">enable true DCI</a>.)</p>
<h3 id="heading-private-rolemethods">Private RoleMethods</h3>
<p>One last thing: A modification made to the code is <code>Messages__set</code>. The double underscore declares a <em>private RoleMethod</em>, which further encapsulates behavior only related to a Role. In this case, <code>Messages.set</code> was made to prevent code duplication, and we don't want other Roles, or the Context itself, to use it. A Role should only expose what's purposeful in its interaction with others in a Context, which raises an important question: <em>If there is only one Role, can there be interaction?</em></p>
<p>This question will be answered in the next part. <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-4">Keep reading in Part 4</a>!</p>
<h2 id="heading-bonus-points">Bonus points</h2>
<p>The only real DCI language out there, trygve, is taking an interesting approach to iterables by defining <em>Role vectors</em>, which map behavior to one item at a time. Read more about it, and about DCI in depth, in the comprehensive trygve user manual, <a target="_blank" href="https://github.com/jcoplien/trygve/blob/master/doc/trygve.md">available here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Maintenance mode on a SvelteKit site]]></title><description><![CDATA[A maintenance mode that disables access to your site can be quite useful for scheduled downtime. The idea is simple, but when we take UX and different kind of HTTP request scenarios into account, we have quite a few things to tackle. Let's try to lis...]]></description><link>https://blog.encodeart.dev/implementing-maintenance-mode-on-a-sveltekit-site</link><guid isPermaLink="true">https://blog.encodeart.dev/implementing-maintenance-mode-on-a-sveltekit-site</guid><category><![CDATA[Sveltekit]]></category><category><![CDATA[Svelte]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[maintenance]]></category><category><![CDATA[forms]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Sat, 29 Oct 2022 11:41:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666970901075/YvXvYtdQO.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A maintenance mode that disables access to your site can be quite useful for scheduled downtime. The idea is simple, but when we take UX and different kind of HTTP request scenarios into account, we have quite a few things to tackle. Let's try to list them first:</p>
<h2 id="heading-users-browsing-the-site">Users browsing the site</h2>
<p>For normal browsing (<code>GET</code> requests), a user-friendly message should be displayed. A thought could be to redirect users to a <code>/maintenance</code> route, but unexpected redirects is not a very nice UX. The route should not change, which means that we should use a common <code>+layout.svelte</code> component to display the message.</p>
<h2 id="heading-api-access">API access</h2>
<p>If your site has an API defined with <code>+server.ts</code> files (<a target="_blank" href="https://kit.svelte.dev/docs/routing#server">docs here</a>) these won't use a layout file, so they must be handled separately. It's probably unlikely that the API will be used for browsing, so a <code>503 Service Unavailable</code> could be returned immediately, which is especially important in this case, since we don't want any API requests to go through and potentially modifying the database when we're doing maintenance!</p>
<h2 id="heading-form-data-posting">Form data posting</h2>
<p>This one is easy to overlook, but possibly the most important: A user posting form data to your site is <strong>very valuable</strong>, they could be finishing up a purchase, for example. So we need to treat these with the highest respect - if their data is lost in any way because of the maintenance, we've probably lost a customer.</p>
<p>This means that we need to:</p>
<ul>
<li>Prevent redirections </li>
<li>Prevent clearing of the form</li>
<li>Display a message that they could try again soon.</li>
</ul>
<p>SvelteKit has the nice feature of <a target="_blank" href="https://kit.svelte.dev/docs/form-actions#progressive-enhancement">Progressive enhancement</a>, which takes user agents with no javascript into account.</p>
<p>Unfortunately, without javascript the browser will reload the page, and unless we do some advanced  checking for maintenance mode later in the request, and restoring the form data without any other action taken, there's not much to do here.</p>
<p>If javascript is enabled though, we can take advantage of the progressive enhancement to prevent data loss, and that's what we will focus on, given that the majority of the users should have javascript enabled. But before we look into that, let's see if we can make things simpler.</p>
<h1 id="heading-simplifying-based-on-request-method">Simplifying based on request method</h1>
<p>A simplification could be made by assuming that <code>GET</code> requests won't modify anything critical in the system. If the site collects advanced statistics this may be a faulty assumption, but noting that, we're left with a simpler use case:</p>
<ol>
<li>If <code>GET</code> - Display a friendly error message</li>
<li>If not <code>GET</code> - Return a JSON error message</li>
</ol>
<p>This works because we want to return a 503 status code in <em>both</em> cases, which will cover our three scenarios:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Browsing</td><td>API request</td><td>AJAX form posting</td></tr>
</thead>
<tbody>
<tr>
<td>GET</td><td>Friendly message</td><td>503 status</td><td>N/A</td></tr>
<tr>
<td>Not GET</td><td>N/A</td><td>503 status</td><td>503 status+JSON</td></tr>
</tbody>
</table>
</div><p>Since HTTP status <code>503</code> means <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503">temporary unavailable</a>, the API consumers should be able to handle that in both cases, even if they get a html page in the case of a GET request. Browsers will display the friendly message either way.</p>
<p>For form posting, returning HTML will mess up the client-side response, which expects JSON (we are ignoring the non-javascript users, as said) so we want to return something that the browser understands. SvelteKit has an <a target="_blank" href="https://kit.svelte.dev/docs/types#sveltejs-kit-actionresult">ActionResult</a> type which seems useful here. Let's piggy-back on that and add the <a target="_blank" href="https://kit.svelte.dev/docs/form-actions#progressive-enhancement-use-enhance">use:enhance</a> action, and we should probably be home free.</p>
<p>But haven't we forgotten something? How do we even put the site into maintenance mode?</p>
<h1 id="heading-setting-the-site-mode">Setting the site mode</h1>
<p>We'd like to set some kind of MODE variable in the system, that will be read very early in the app and handled accordingly. This is not just about <code>maintenance</code> mode, but others as well. For example:</p>
<ul>
<li><code>development</code> mode will have debug info and tools enabled</li>
<li><code>staging</code> mode will have source maps and design feedback tools enabled</li>
<li><code>production</code> mode will have bug reporting tools and analytics enabled</li>
</ul>
<p>There could be all sorts of combinations between these that must be configured depending on the mode.</p>
<p>With NodeJS, Vite and SvelteKit we have a couple of modes already available, but there are a few issues with them:</p>
<ul>
<li>The <code>NODE_ENV</code> environment variable - but setting it to anything else than <code>production</code> or <code>development</code> could be <a target="_blank" href="https://rafaelalmeidatk.com/blog/why-you-should-not-use-a-custom-value-with-node-env">problematic</a></li>
<li>Vite's <code>MODE</code>, available through <code>import.meta.env.MODE</code> - could be anything, but must be set at build-time, so we can't change it while the site is running.</li>
</ul>
<p>It looks like we have no other choice than to use something else. You have probably figured out that environment variables is perfect for this, given their speed of access and the multiple ways they can be updated.</p>
<p>But what should it be called? Naming is always hard. Using <code>MODE</code> directly can create confusion with the Vite variable. So I'm going for <code>APP_MODE</code>, and will specify all valid values in a template <code>.env</code> file:</p>
<p><strong>.env</strong></p>
<pre><code class="lang-bash"><span class="hljs-comment"># development, staging, production, maintenance</span>
APP_MODE=<span class="hljs-string">"development"</span>

<span class="hljs-comment"># development, production</span>
NODE_ENV=<span class="hljs-string">"development"</span>
</code></pre>
<p>(<code>NODE_ENV</code> is undefined as default, so we'll set that one too.)</p>
<p>Finally, we're ready to code!</p>
<h1 id="heading-starting-on-top-with-hooks">Starting on top with hooks</h1>
<p>Catching requests early is best done through <code>src/hooks.server.ts</code> in your SvelteKit app. This is where we want to respond with a 503 and a friendly message in HTML or JSON format, depending on request method. Let's dig in! The code <a target="_blank" href="https://stackblitz.com/edit/sveltekit-maintenance-mode?file=src/routes/+page.svelte">is available on Stackblitz</a>.</p>
<p><strong>src/hooks.server.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { env } <span class="hljs-keyword">from</span> <span class="hljs-string">'$env/dynamic/private'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Handle } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sveltejs/kit'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { ActionResult } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sveltejs/kit'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> handle: Handle = <span class="hljs-keyword">async</span> ({ event, resolve }) =&gt; {
  <span class="hljs-keyword">if</span> (env.APP_MODE !== <span class="hljs-string">'maintenance'</span>) <span class="hljs-keyword">return</span> resolve(event);

  <span class="hljs-comment">// Non-GET requests should not pass through.</span>
  <span class="hljs-keyword">if</span> (event.request.method !== <span class="hljs-string">'GET'</span>) {
    <span class="hljs-keyword">const</span> error = { status: <span class="hljs-number">503</span>, statusText: <span class="hljs-string">'Maintenance mode.'</span> };
    <span class="hljs-keyword">const</span> actionResult: ActionResult = { <span class="hljs-keyword">type</span>: <span class="hljs-string">'error'</span>, error };
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify(actionResult), {
      ...error,
      headers: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> }
    });
  }

  <span class="hljs-comment">// For GET requests, +layout.svelte will show a friendly error,</span>
  <span class="hljs-comment">// so we should render the response and make sure that maintenance </span>
  <span class="hljs-comment">// mode is respected there as well.</span>
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> resolve(event);

  <span class="hljs-comment">// Replace appropriate parts of the response with 503.</span>
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(response.body, {
    headers: response.headers,
    status: <span class="hljs-number">503</span>,
    statusText: <span class="hljs-string">'Maintenance mode'</span>
  });
};
</code></pre>
<h1 id="heading-displaying-the-message-in-layoutsvelte">Displaying the message in +layout.svelte</h1>
<p>As said, we need to use <code>+layout.svelte</code> to display the message, but there is a problem: We can't access <code>APP_MODE</code> directly from there since it's a server-side variable only. And we probably don't want to expose that value to the world, so we need a <code>+layout.server.ts</code> file to read and transform that value:</p>
<p><strong>src/routes/+layout.server.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { LayoutServerLoad } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
<span class="hljs-keyword">import</span> { env } <span class="hljs-keyword">from</span> <span class="hljs-string">'$env/dynamic/private'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> load: LayoutServerLoad = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">switch</span> (env.APP_MODE) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'staging'</span>:
    <span class="hljs-keyword">case</span> <span class="hljs-string">'production'</span>:
    <span class="hljs-keyword">case</span> <span class="hljs-string">'development'</span>:
      <span class="hljs-keyword">return</span> { maintenance: <span class="hljs-literal">false</span> };

    <span class="hljs-keyword">case</span> <span class="hljs-string">'maintenance'</span>:
      <span class="hljs-keyword">return</span> { maintenance: <span class="hljs-literal">true</span> };

    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid app mode!'</span>);
  }
};
</code></pre>
<p>This helps us ensure that <code>APP_MODE</code> is set to a correct value. The returned data structure can also be used for other cases, for example adding external javascript in certain modes. Using it is very simple:</p>
<p><strong>src/routes/+layout.svelte</strong></p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> type { LayoutData } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> data: LayoutData;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

{#if data.maintenance}
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Maintenance mode<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>We'll be back in a few minutes.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
{:else}
  <span class="hljs-tag">&lt;<span class="hljs-name">slot</span> /&gt;</span>
{/if}
</code></pre>
<p>With this, the users can simply reload the site when the message appears, instead of being redirected somewhere else and losing the current URL.</p>
<p>So we have covered API usage and browsing. Now let's tackle the more difficult, but very important part - form posting.</p>
<h1 id="heading-preventing-data-loss-at-forms">Preventing data loss at forms</h1>
<p>We all know how annoying this can be, so it must be prevented. Let's test it by setting up a simple form on the default page, with a form action:</p>
<p><strong>src/routes/+page.svelte</strong></p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">import</span> type { ActionData } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
  <span class="hljs-keyword">import</span> { enhance, applyAction } <span class="hljs-keyword">from</span> <span class="hljs-string">'$app/forms'</span>;
  <span class="hljs-keyword">import</span> { page } <span class="hljs-keyword">from</span> <span class="hljs-string">'$app/stores'</span>;

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">let</span> form: ActionData;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span> <span class="hljs-attr">use:enhance</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Name: <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{form?.name</span> ?? ""} /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  {#if form?.errors?.name}
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Name must be at least 4 characters long.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  {/if}

  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p><strong>src/routes/+page.server.ts</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { Actions } <span class="hljs-keyword">from</span> <span class="hljs-string">'./$types'</span>;
<span class="hljs-keyword">import</span> { invalid } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sveltejs/kit'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> actions: Actions = {
  <span class="hljs-keyword">default</span>: <span class="hljs-keyword">async</span> (event) =&gt; {
    <span class="hljs-comment">// For testing:</span>
    <span class="hljs-comment">// throw new Error('Simulating error/maintenance mode')</span>

    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> event.request.formData();

    <span class="hljs-keyword">const</span> values = {
      name: data.get(<span class="hljs-string">'name'</span>)?.toString() ?? <span class="hljs-string">""</span>
    };

    <span class="hljs-keyword">const</span> errors = {
      name: values.name.length &lt; <span class="hljs-number">4</span>
    };

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Object</span>.values(errors).some(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> e)) {
      <span class="hljs-keyword">return</span> invalid(<span class="hljs-number">400</span>, { ...values, errors });
    }

    <span class="hljs-keyword">return</span> { status: <span class="hljs-string">'success'</span> };
  }
};
</code></pre>
<p>This should work as expected, but if we uncomment the <code>throw new Error</code> line, or set <code>APP_MODE=maintenance</code> (which is difficult without making a production build, hence the <code>throw</code> shortcut), we're directly taken to the error page, losing all form data.</p>
<p>This means that we have to use the callback for <code>use:enhance</code> to avoid redirection. Change the form element to this:</p>
<p><strong>src/routes/+page.svelte</strong></p>
<pre><code class="lang-typescript">&lt;form method=<span class="hljs-string">"POST"</span> use:enhance={<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> ({form, result}) =&gt; {
    <span class="hljs-keyword">if</span>(result.type !== <span class="hljs-string">"error"</span>) {
      <span class="hljs-keyword">if</span>(result.type === <span class="hljs-string">'success'</span>) form.reset()
      applyAction(result)
    }
    <span class="hljs-keyword">else</span> applyAction({
      <span class="hljs-keyword">type</span>: <span class="hljs-string">'invalid'</span>,
      status: <span class="hljs-built_in">Math</span>.floor(result.error.status) || <span class="hljs-number">500</span>
    })
  }
}}&gt;
</code></pre>
<p>This will update the form as usual unless an error occurs, in which case it will treat the result as an <code>invalid</code> state, which will not render the error page. The <code>Math.floor</code> trickery is to ensure an integer for the status value (<a target="_blank" href="https://dev.to/sanchithasr/7-ways-to-convert-a-string-to-number-in-javascript-4l">thanks Sanchithasr</a>).</p>
<p>Try it out, and you'll see that we're not redirected anymore, but there is nothing notifying the user that the site is down. Fortunately we have <code>$page.status</code> available that can be used for this. We could even use the <code>$page.form</code> to display a success message while we're at it. Add this above the form:</p>
<p><strong>src/routes/+page.svelte</strong></p>
<pre><code class="lang-html">{#if $page.status &gt;= 500}
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">b</span>&gt;</span>Server error, please wait a bit and try again.<span class="hljs-tag">&lt;/<span class="hljs-name">b</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
{:else if $page.form &amp;&amp; $page.status == 200}
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Form posted successfully!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
{/if}
</code></pre>
<h1 id="heading-final-notes">Final notes</h1>
<p>It was quite a bit of work to have a maintenance mode that will cause the least amount of annoyance for our users. The <code>use:enhance</code> part could be factored out into its own function so it can be used on all forms, but I think it its default behavior should be not to "redirect" to an error page, since staying on the same page without reload is one of the points of using <code>use:enhance</code>! I've posted <a target="_blank" href="https://github.com/sveltejs/kit/issues/7429">an issue about it</a> on Github, so we'll see what the kind SvelteKit team says.</p>
<p><strong>About API requests:</strong> In case you really want to return JSON with API requests instead of the layout html, hopefully your API uses a route like <code>/api/...</code>, which mean that you can test for that in <code>hooks.server.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (event.request.method !== <span class="hljs-string">'GET'</span> || event.routeId.startsWith(<span class="hljs-string">'/api/'</span>)
</code></pre>
<p>A final thing that we haven't tackled is to make the form status message disappear when re-submitting the data. Again, it's nice UX to make the message disappear/change while waiting for the response, but I haven't found a way to handle this without a separate variable on the page, which would be nice to avoid. Let me know if you have a way of solving this.</p>
<p>We're at the end, so thanks for reading, I hope you can find this useful. Thoughts, comments, suggestions, please let me know!</p>
]]></content:encoded></item><item><title><![CDATA[DCI tutorial for TypeScript: Part 2]]></title><description><![CDATA[Welcome back to the DCI tutorial series for Typescript! If you're new, check out part 1 first.
This second part is more theoretical, but some background is needed to understand why we should consider coding in a quite different way than what we are u...]]></description><link>https://blog.encodeart.dev/dci-tutorial-for-typescript-part-2</link><guid isPermaLink="true">https://blog.encodeart.dev/dci-tutorial-for-typescript-part-2</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Objects]]></category><category><![CDATA[dci]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Tue, 25 Oct 2022 19:03:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666722506494/dAafFp2HI.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome back to the DCI tutorial series for Typescript! If you're new, <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-1">check out part 1</a> first.</p>
<p>This second part is more theoretical, but some background is needed to understand why we should consider coding in a quite different way than what we are used to.</p>
<h1 id="heading-who-do-we-code-for">Who do we code for?</h1>
<p>I think it's fair to say that most of the time, we code for others. All but the tiniest of hobby projects will eventually reach someone else than the programmer - a user. From that point of view, a priority should be to design software that is both understandable and usable for those people that aren't programmers.</p>
<p>Once upon a time, this was common sense. Back in the 70s, in Palo Alto, people like Alan Kay were researching how to make computers as easy as possible to use, with the vision that the computer, with its speed and processing power, could be an extension of the human mind. It was an exciting time, when design, psychology and other disciplines melted together in this new medium.</p>
<p>The key idea was <em>how to make the computer think as the user</em>, not the other way around, as unfortunately happened when engineers after the 70s decided that their structures are what software should be based upon, not the user's mind[<a class="post-section-overview" href="#heading-footnotes">1</a>]. This is something that DCI tries to remedy.</p>
<h1 id="heading-dci-and-mental-models">DCI and mental models</h1>
<p>If we could design a system that reflects the user's mind in program code, that would be a huge step forward. The programs would look different as well, since you cannot find many users that speak about programming-related things like "abstract factories", "visitors", "polymorphism" or even "return values" when they describe their view of a system.</p>
<p>Therefore, if we want to <em>make the computer think as the user</em>, we need to put things like patterns and classes in the back seat, or at least consider them as plumbing - useful, but hidden and not in the way of the user and the architecture that we're trying to build.</p>
<p>With this in mind, let's see how a user could describe a program:</p>
<blockquote>
<p>"I want to proclaim something to the world."<br />Like a phrase?<br />"Yes, I want to proclaim a phrase to the world!"<br />And who are you?<br />"I'm a speaker, and I want to proclaim a phrase to the world."<br />What should the world do with your proclamation?<br />"It should listen to it."<br />What if it won't?<br />"Well, it should at least note it."</p>
</blockquote>
<p>Let's compare that to a slightly modified version of the Context from part 1:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * @DCI-context
 * A speaker proclaims a phrase to the world, that dutifully notes it
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">HelloWorld</span>(<span class="hljs-params">
  <span class="hljs-comment">// Roles - Objects required for the Context functionality</span>
  Speaker: { phrase: <span class="hljs-built_in">string</span> },
  World: { log: (msg: unknown) =&gt; <span class="hljs-built_in">void</span> }
</span>) </span>{
  <span class="hljs-comment">// RoleMethods - Describing the interaction between objects</span>

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker_proclaim</span>(<span class="hljs-params"></span>) </span>{
    World_note(Speaker.phrase);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">World_note</span>(<span class="hljs-params">phrase: <span class="hljs-built_in">string</span></span>) </span>{
    World.log(phrase);
  }

  {
    <span class="hljs-comment">// System operation - Starts execution of the Context</span>
    Speaker_proclaim();
  }
}
</code></pre>
<p>Looking at the RoleMethods, we have a <code>Speaker</code> that proclaims a phrase, and a <code>World</code> that notes it. Seems to be quite a match of the description, doesn't it?</p>
<p>What we have done is expressed in code the user's <em>mental model</em> of a Hello World program. The Wikipedia definition of a Mental model as <strong>"an explanation of someone's thought process about how something works in the real world"</strong> works in this case, but only scratches the surface. Indi Young puts it in much more detail in her excellent book "Mental Models":</p>
<blockquote>
<p>"Designing something requires that you completely understand what a person wants to get done. [...] You need to know the person’s goals and what procedure and philosophy she follows to accomplish them. Mental models give you a deep understanding of people’s motivations and thought-processes, along with the emotional and philosophical landscape in which they are operating."</p>
</blockquote>
<p>In a mental model, the user quite consistently speaks about objects as Roles. Let's examine that.</p>
<h1 id="heading-the-difference-between-thinking-and-doing-for-users">The difference between <em>thinking</em> and <em>doing</em> for users</h1>
<p>Remember how we executed this Context? We passed two simple objects to it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> you = { phrase: <span class="hljs-string">"Hello, World!"</span> }

HelloWorld(you, <span class="hljs-built_in">console</span>)
</code></pre>
<p>However, in this Context, <code>you</code> transform into a <code>Speaker</code> and the <code>console</code> into the <code>World</code>. This is a key difference between <em>thinking</em> and <em>doing</em>. When a user thinks about something (data), objects become what they are. <code>You</code> is a simple object that represents a person with a phrase. But when it's time to do something (function), we label things differently. The person-object plays the Role of a <code>Speaker</code>, and the <code>console</code> isn't just a computer screen anymore, according to the user it's the <code>World</code>!</p>
<p>You could say that we are upholding an illusion here, just as an actor playing a believable Hamlet is upholding that illusion, to the enjoyment of the audience. And this is what happens when the mental models match. When you play a computer game and the controls are matching your idea of piloting a starship, the controller becomes an extension of your mind, and the illusion of being a starship pilot is upheld, for your enjoyment. Which was the goal of the humane computer research, back in the 70s.</p>
<p>Welcome to DCI, where we express and uphold mental models for the sake of the most important part of the system - the user.</p>
<p>In the next part, we will get much more practical with modeling a realistic example of a DCI Context. <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-3">Keep reading in Part 3</a>.</p>
<h1 id="heading-footnotes">Footnotes</h1>
<p>[<a class="post-section-overview" href="#heading-who-do-we-code-for">1</a>] For a deeper dive into users and computers, see my article <a target="_blank" href="https://blog.encodeart.dev/rediscovering-mvc">Rediscovering MVC</a>.</p>
]]></content:encoded></item><item><title><![CDATA[DCI tutorial for TypeScript: Part 1]]></title><description><![CDATA[DCI (Data, Context and Interaction) is a programming paradigm that breathes life into the hope that we can someday write maintainable software. This tutorial series will provide both an in-depth explanation of the DCI concepts, as a programmer would ...]]></description><link>https://blog.encodeart.dev/dci-tutorial-for-typescript-part-1</link><guid isPermaLink="true">https://blog.encodeart.dev/dci-tutorial-for-typescript-part-1</guid><category><![CDATA[dci]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[eslint]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Sat, 22 Oct 2022 15:29:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1666451821094/KxcgwmCT6.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>DCI (Data, Context and Interaction)</strong> is a programming paradigm that breathes life into the hope that we can someday write maintainable software. This tutorial series will provide both an in-depth explanation of the DCI concepts, as a programmer would like to understand, but also a high-level perspective explaining why we should consider writing code in a different way than what we do today.</p>
<h1 id="heading-what-is-dci">What is DCI?</h1>
<p>DCI was invented by Trygve Reenskaug, also the inventor of MVC, and has been furthered together with renowned OOP writer James O. Coplien since 2009.</p>
<p>From a high level, DCI strives to achieve the following:</p>
<ul>
<li><p>Separating what the system is (data) from what it does (function). Data and function have different rates of change so they should be separated, not as it currently is, put in classes together.</p>
</li>
<li><p>Create a direct mapping from the user's mental model to code. The computer should think as the user, not the other way around.</p>
</li>
<li><p>Make system behavior a first class entity.</p>
</li>
<li><p>Great code readability with no surprises at runtime.</p>
</li>
</ul>
<p>To begin our journey, let's start with the <strong>Context</strong>, since from a programmer's perspective, most of the DCI-related things happen in there.</p>
<h1 id="heading-the-dci-context">The DCI Context</h1>
<p>A DCI Context is a structure that encapsulates system behavior, in contrast to objects and classes which encapsulate state well, but only capture fragments of the interactions between objects in the system, which makes a system more difficult to reason about as it grows.</p>
<p>One of the goals of DCI is to prevent the spreading of functionality across arbitrary boundaries, be it a class hierarchy, helpers, services, patterns, etc. Many programming constructs abstract away relevant information (like interfaces), or provides irrelevant information (classes), so we are left with either a limited or overwhelming view when trying to understand how the system works at runtime.</p>
<p>The current way of gaining an understanding of runtime system behavior is to use a debugger, or become the debugger by tracing the program in your head, branching and jumping across class hierarchies, events, exceptions, etc. Something is obviously problematic with this hope that the parts of a system will self-organize, yet every torturous visit to the debugger proves otherwise.</p>
<p>Fortunately, a DCI Context not only encapsulates but also describes the runtime behavior of a network of interaction objects in a relevant locus, without hiding information behind classes, interfaces and polymorphism. So how does it work?</p>
<h2 id="heading-a-basic-context">A basic Context</h2>
<p>A true DCI execution model is difficult to achieve today due to several technical reasons, but I've created a TypeScript library that tries to get close by making the code adhere to certain conventions. It can be installed by following the instructions at <a target="_blank" href="https://github.com/ciscoheat/eslint-plugin-dci-lint">https://github.com/ciscoheat/eslint-plugin-dci-lint</a></p>
<p>If you want to follow this tutorial in code, you can do that with Stackblitz <a target="_blank" href="https://stackblitz.com/edit/dci-ts?file=src%2Fmain.ts,src%2FHelloWorld.ts">by clicking here</a>.</p>
<p>The library tries to provide the simplest possible structure for a working DCI Context, within the limits of TypeScript syntax. Here's an example of a "Hello World" Context:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * @DCI-context
 * A speaker proclaims something to the world, that dutifully notes it
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">HelloWorld</span>(<span class="hljs-params">
  Speaker: { phrase: <span class="hljs-built_in">string</span> },
  World: { log: (msg: unknown) =&gt; <span class="hljs-built_in">void</span> }
</span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker_proclaim</span>(<span class="hljs-params"></span>) </span>{
    World_note(Speaker.phrase);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">World_note</span>(<span class="hljs-params">phrase: <span class="hljs-built_in">string</span></span>) </span>{
    World.log(phrase);
  }

  Speaker_proclaim();
}
</code></pre>
<p>First, the <code>@DCI-context</code> tag on top is required for the library to work. It can be put in a simpler <code>// @DCI-context</code> comment as well, right above the function.</p>
<p>The Context may look a bit unusual, due to the capitalized variables and underscored methods. The underscore is a syntax limitation that will be explained later, but capitalization is only a convention. UPPERCASE has been used in other examples, similar to a movie script that uses uppercase to highlight importance, but it may be too sore on the eyes, so capitalization is a middle ground.</p>
<p>Looking at the Context, two often mentioned parts seem to be <code>Speaker</code> and <code>World</code>. What are they? They're not classes, since they are typed as literal objects. You don't have to look up the public interface of a class or type in a different file to understand what they can do. As said, a goal with DCI is to be able to understand the code from a different perspective - in a Context we're standing <em>between</em> objects, describing and observing how they interact, only being interested in what's relevant in the context, in this case, proclaiming something to the world.</p>
<p>This is in contrast to traditional OOP, where we are placed <em>inside</em> the objects, only seeing a limited view of the system (the class interface and its outgoing messages).</p>
<h1 id="heading-a-brief-introduction-to-roles">A brief introduction to Roles</h1>
<p>Again, what is the <code>Speaker</code> and the <code>World</code>? The answer is that they are <strong>Roles</strong>. A Role is an identifier within a Context, with a literal type, called a <em>RoleObjectContract</em>, or just <em>contract</em>.</p>
<p>Like in a movie, a Role can be played by an actor - in our case an object, but just as real actors casting for a certain role, the object must adhere to certain characteristics. Here are the Roles for HelloWorld again:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * @DCI-context
 * A speaker proclaims something to the world, that dutifully notes it
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">HelloWorld</span>(<span class="hljs-params">
  Speaker: { phrase: <span class="hljs-built_in">string</span> },
  World: { log: (msg: unknown) =&gt; <span class="hljs-built_in">void</span> }
</span>)</span>
</code></pre>
<p>Looking at <code>Speaker</code>, from its contract we discern that any object with a <code>string</code> property called <code>phrase</code> can play the Role of <code>Speaker</code>. Let's create a super-simple object just for that purpose:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> you = { phrase: <span class="hljs-string">"Hello, World!"</span> };
</code></pre>
<p>The contract for the <code>World</code> Role is a <code>log</code> function that accepts anything and returns nothing. With a bit of knowledge about Javascript runtimes, we know that the global <code>console</code> object matches that contract.</p>
<p><strong>About naming:</strong> Why are the Roles in this Context named "Speaker" and "World"? That has to do with the mental model the Context is based upon, which will be the topic of the next part. Until then, just note that the comment above the Context mentions these two names.</p>
<h1 id="heading-the-data-part-of-dci">The Data part of DCI</h1>
<p>The objects <code>you</code> and <code>console</code> are the <em>Data</em> part of the DCI acronym; simple objects with little awareness of the rest of the system, which is very well, because in DCI we don't want to mix Data (what the system <em>is</em>) with Functionality (what the system <em>does</em>). Not only do they have different rates of change, so a domain separation is already obvious, but mixing them leads to the classic issue of objects taking on more and more responsibilities, eventually resulting in an entangled and fragile jumble.</p>
<h1 id="heading-rolemethods">RoleMethods</h1>
<p>Looking at the <code>HelloWorld</code> Context again, we see an underscored method:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker_proclaim</span>(<span class="hljs-params"></span>) </span>{
  World_note(Speaker.phrase);
}
</code></pre>
<p>The underscore, being a rather poor substitute for a period due to syntax limitations, demarcates a <em>RoleMethod</em>. Its name always starts with one of the Roles in the Context, working as an extension of the object playing the Role while it's inside the Context. See it as the object playing the Role of <code>Speaker</code> gets a <code>proclaim()</code> function attached to it when, and only when, the <code>HelloWorld</code> Context is executing.</p>
<h1 id="heading-the-interaction-part-of-dci">The Interaction part of DCI</h1>
<p>In the function body of <code>Speaker_proclaim</code>, we see that the <code>Speaker</code> interacts with the <code>World</code> Role, by calling one of its RoleMethods:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker_proclaim</span>(<span class="hljs-params"></span>) </span>{
  World_note(Speaker.phrase);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">World_note</span>(<span class="hljs-params">phrase: <span class="hljs-built_in">string</span></span>) </span>{
  World.log(phrase);
}
</code></pre>
<p>In <code>World_note</code>, the <code>World</code> calls its <code>log</code> method, and this is a very important part of DCI - <em>only the Role can access its contract</em>. It solely decides when to call its underlying object, inside its own RoleMethods. This is one of several rules that the linter library is upholding. For example, this would lead to an error:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Trying to take a shortcut:</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">World_note</span>(<span class="hljs-params"></span>) </span>{
  World.log(Speaker.phrase); <span class="hljs-comment">// Error: Call to a Role contract outside its own RoleMethods.</span>
}
</code></pre>
<p>By preventing access to the underlying objects from other Roles, we gain additional security. Just as computer memory is protected by an operating system, we shouldn't be able to dive into the internals of any object in our system without consideration, and being explicit about it.</p>
<h1 id="heading-executing-the-context-system-interaction">Executing the Context - System interaction</h1>
<p>At the end of the Context, there is an entry point for its execution:</p>
<pre><code class="lang-typescript">Speaker_proclaim();
</code></pre>
<p>This is called a <em>System Interaction</em> which starts a flow of interactions through the Context by calling a RoleMethod. We can do this in two different ways:</p>
<p><strong>A.</strong> When the Context describes functionality that should execute "on the spot", like in this example, putting a RoleMethod at the end is a simple way of starting it. Using our objects <code>you</code> and <code>console</code>, it will look just like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> you = { phrase: <span class="hljs-string">"Hello, World!"</span> };

HelloWorld(you, <span class="hljs-built_in">console</span>);
</code></pre>
<p><strong>B.</strong> If you instead want to reuse the context, or pass parameters to it, you can achieve a more OO approach by letting it return an object, basically the public interface of the Context:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">HelloWorld</span>(<span class="hljs-params">
  Speaker: { phrase: <span class="hljs-built_in">string</span> },
  World: { log: (msg: unknown) =&gt; <span class="hljs-built_in">void</span> }
</span>) </span>{
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Speaker_proclaim</span>(<span class="hljs-params"></span>) </span>{
    World_note(Speaker.phrase);
  }

  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">World_note</span>(<span class="hljs-params">phrase: <span class="hljs-built_in">string</span></span>) </span>{
    World.log(phrase);
  }

  <span class="hljs-keyword">return</span> {
    note: <span class="hljs-function">() =&gt;</span> Speaker_proclaim()
  }
}
</code></pre>
<p>Which will make the Context usable as an object:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> you = { phrase: <span class="hljs-string">"Hello, World!"</span> };
<span class="hljs-keyword">const</span> world = HelloWorld(you, <span class="hljs-built_in">console</span>);

world.note();
</code></pre>
<p>This concludes part 1 of this DCI tutorial! The HelloWorld Context is a bit contrived, but the next part will explain why methods like <code>Speaker.exclaim</code> and <code>World.note</code> are important, instead of a simple <code>console.log(you.phrase)</code>. Please continue <a target="_blank" href="https://blog.encodeart.dev/dci-tutorial-for-typescript-part-2">in part 2</a>.</p>
<h2 id="heading-official-website">Official website</h2>
<p>For more information about DCI, its official website is <a target="_blank" href="https://fulloo.info">https://fulloo.info</a>.</p>
]]></content:encoded></item><item><title><![CDATA[It's time to sandbox the software ecosystem]]></title><description><![CDATA[There is something notable about software development culture. Having recently watched The Code Report about a fresh new web framework on a fresh new runtime, as mentioned in the comments there, we have moved from PHP+jQuery to fully dynamic SPA:s, a...]]></description><link>https://blog.encodeart.dev/its-time-to-sandbox-the-software-ecosystem</link><guid isPermaLink="true">https://blog.encodeart.dev/its-time-to-sandbox-the-software-ecosystem</guid><category><![CDATA[javascript framework]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Javascript library]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Tue, 26 Jul 2022 09:18:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659007133718/IyCMiXk3z.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>There is something notable about software development culture.</strong> Having recently watched The Code Report about <a target="_blank" href="https://www.youtube.com/watch?v=4boXExbbGCk">a fresh new web framework on a fresh new runtime</a>, as mentioned in the comments there, we have moved from PHP+jQuery to fully dynamic SPA:s, and seemingly back again.</p>
<p>In a similar vein, programmers have gone from command line-loving basement dwellers to hipster rockstars, and now back to some modern, shaped up version of the former, that loves over-engineered solutions and spending 25 hours a day delving into every single detail of the hoops you have to jump through to get something working, as well as <em>comparing</em> all these frameworks. And as soon as they grasp the concept, they excitingly reinvent the wheel, adding to the already bizarre amount of libraries and packages.</p>
<p>Unfortunately they do this without long-term thinking, as we can surely see by the amount of libraries and frameworks that ends up abandoned or decidedly "not such a good idea" by consensus, after a brief period of time.</p>
<p>For example, which validation library should I use? Joi, Zod, Ajv or Yup? They all look similar, do similar things, have a similar API, similar sponsors, similar documentation, and a similar list of similar companies using them. I don't want to spend my days digging through these or every other part of the project that needs a library. <em>It's so rare that these are new problems.</em> There should already be an effective, established general solution, and all that the library programmers would have to do is to port it to their favorite language and platform. But following a dry specification is tedious compared to the fun part of letting your imagination break the mould, so <em>"I want to use my own ideas!"</em> the nerd youthfully exclaims, while writing yet another HTTP client.</p>
<p>There's nothing wrong with that as a learning process, but what's happening in the development world is turning into navel-gazing and a waste of time and money. We code mainly for others, and others usually pay us for doing a good job in good time. Reinventing wheels is not included in that. <strong>Therefore, the nerds need a wake-up call</strong>. A way to do this could be for the package managers to have a sandbox and strict version control. This is the message you should get when you've uploaded a package:</p>
<p><em>"Thanks for your contribution! You're gonna be in this hidden section for some time, whether you're backed by Facebook or not. The only way for the public to use your library in the first year is by adding some complicated flags to their project, which should be no problem for configuration-loving nerds. Warnings will be displayed when doing that, of course. Our AI will also link your project to similar, public ones, and also to the closest related standard, with helpful suggestions how you can adhere closer to it. After a year, if you're still around and decide to publish, your API must be set to version 1.0.0+, and if you break it, you're back in the sandbox."</em></p>
<p>Who will answer to this call? I can't myself unfortunately, I'm stuck being a human API decoder and comparer of frameworks...</p>
]]></content:encoded></item><item><title><![CDATA[Rediscovering MVC - A severely misunderstood "pattern"]]></title><description><![CDATA[(Originally posted by me on github, now updated and moved to a better writing platform)
Poor MVC! It's probably the most misunderstood "pattern" in the history of computer science.
Why? Viewing MVC as simply separation of concerns leads to unnecessar...]]></description><link>https://blog.encodeart.dev/rediscovering-mvc</link><guid isPermaLink="true">https://blog.encodeart.dev/rediscovering-mvc</guid><category><![CDATA[mvc]]></category><category><![CDATA[Objects]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Andreas Söderlund]]></dc:creator><pubDate>Sat, 05 Mar 2022 14:34:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646469721703/NdRFHxDwa.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>(Originally posted by me on github, now updated and moved to a better writing platform)</em></p>
<p>Poor MVC! It's probably the most misunderstood "pattern" in the history of computer science.</p>
<p>Why? Viewing MVC as simply separation of concerns leads to unnecessarily complicated solutions to simple problems. MVC is richer than a single pattern - it is a <em>pattern language</em>, created to align the computer and the programmer with the mental model of the end user.</p>
<p>So if you want to expand your view on a very useful and well researched pattern language, let's try to understand it again, beginning with a visit to the past.</p>
<h1 id="heading-a-brief-history-of-patterns">A brief history of Patterns</h1>
<h3 id="heading-70s">70s</h3>
<p>When <a target="_blank" href="https://en.wikipedia.org/wiki/Trygve_Reenskaug">Trygve Reenskaug</a> started his research back in the 70s, great ideas were going on in computer science. No doubt computers were here to stay, and people like <a target="_blank" href="https://en.wikipedia.org/wiki/Alan_Kay">Alan Kay</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Douglas_Engelbart">Doug Engelbart</a> had visions of computers being an extension of the human mind. The computer was supposed to be composed of objects, a concept that humans reasoned about naturally, and those objects were supposed to communicate with each other, essentially making the objects a recursion on the idea of the computer itself. So Trygve set out to make a connection between computer data and stuff in the end user's head, trying to make the computer think what the user was thinking.</p>
<p>The result, conceived in 1978, was MVC, Model-View-Controller. It was based on how humans interpret and manipulate data and information, and it seemed possible for software to take a big step towards object-orientation and user-friendliness. The computer was in grasp of any person!</p>
<h3 id="heading-80s">80s</h3>
<p>Then came the 80s, and the engineers were spending time with what they enjoyed, which wasn't end user mental models, dynamic interactions, user interfaces and other human-related things. No, it was <em>structure</em>. After some time, the actual architecture, the <em>form</em> of a system relevant for the users, was completely hidden by layers of abstraction called <a target="_blank" href="https://en.wikipedia.org/wiki/Software_design_pattern">software design patterns</a>, later known as the "<a target="_blank" href="https://en.wikipedia.org/wiki/Design_Patterns">GoF patterns</a>". They were abstract, decoupled and reusable, like a house made entirely out of scaffolding. Ugly and not user friendly, but very organized, just like the engineers want it!</p>
<h3 id="heading-90s">90s</h3>
<p>The GoF patterns became the norm, and in the 90s the humane ideas from the 70s were mostly forgotten. Nerds ruled the computer world, and nobody else had anything to say about software design. Who could even argue? Computers were considered very complicated, no wonder when the engineers had turned the rules around. The users were basically controlled by the computer and its programmers, and most systems turned very complex (big structure) or into a mess (bad structure).</p>
<h3 id="heading-00s">00s</h3>
<p>In the 00s, after the dot-com bubble, the costs of this structural mess had become a real issue. This led to the idea that we shouldn't even consider architecture and planning anymore. Just be Agile! Make the structure so flexible that if any new requirements comes up, we can just add more with almost no cost. If this adding sums up to a mountain of scaffold, well, another layer of indirection/scaffold will surely solve it! Then introduce unit testing, a very[<a target="_blank" href="https://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf">1</a>] wasteful[<a target="_blank" href="https://www.rbcs-us.com/documents/Segue.pdf">2</a>] testing method that will effectively double the code base (how Agile is that?), and not much have changed really.</p>
<p>But something happened around here, when the web started to grow rapidly. MVC was rediscovered as a useful pattern for websites. But alas, shoehorned into a design pattern. The idea of separation and modularity was more popular than ever, like the world was made of Lego and block modules was the best way to build a nice, lively house. Separation was enforced, and the View became an HTML container, the Controller not much more than a static class, and the model got the rest of the system, a substantial mix of data and functionality. And the debate started to rage... Fat models? Skinny models? View models? Fat Controllers? Services? Layers? Mediators? Where to put everything?</p>
<h3 id="heading-10s">10s</h3>
<p>No wonder people eventually got tired of this and tried to find alternative patterns and solutions (MVVM, MVP, PAC, MVA, ADR...), but blaming MVC for any of this is not fair, as you hopefully understand now. So let's move on from this history lesson and see what we can do to restore MVC to its former glory!</p>
<h1 id="heading-correcting-some-misconceptions">Correcting some misconceptions</h1>
<p>Let's start by forgetting most of what we know about MVC from thousands of web articles. Unfortunately it's necessary, most are based on the "design pattern" view of MVC, the nerd-centric approach. If we want a way out of the above mess, we have to reconnect to the end user.</p>
<h2 id="heading-mental-models">Mental models</h2>
<p>A <a target="_blank" href="https://en.wikipedia.org/wiki/Mental_model">mental model</a> is "an explanation of someone's thought process about how something works in the real world." This is what Reenskaug had in mind with MVC:</p>
<blockquote>
<p>We have the <em>User's mental model</em>, where the View is a bridge between the Model and the User. The user observes the Model through the View and gives input through the View to the Model, experiencing an illusion of working directly with his or her information (the Model). This means that the Views appear to communicate directly with the Model. The illusion can only be upheld if all Views are synchronized to always show the current Model. How this is done is an implementation detail.</p>
<p>The fundamental idea for the <em>programmer's mental model</em> was to separate the computer representation of the user's mental information model (the Model) from the parts of the program that facilitate the user observing and editing selected aspects of it, the Views. Views are created and coordinated by a Controller. Any actual message flow will be acceptable as long as it upholds the user's mental model.</p>
</blockquote>
<p>This is very welcome information that helps correct some misconceptions about MVC. What else can we find? In Reenskaug's human centered document <a target="_blank" href="https://folk.universitetetioslo.no/trygver/2003/javazone-jaoo/MVC_pattern.pdf">MVC - Its Past and Present</a>, it is shown how MVC works as a pattern language, and there we'll make some other discoveries:</p>
<h2 id="heading-1-objects-can-be-any-combination-of-m-v-and-c">1. Objects can be any combination of M, V and C!</h2>
<p>In <em>MVC - Its Past and Present</em> on page 10-11, we find something ignored by countless programmers who have been enforcing separation at all costs:</p>
<ul>
<li>In simple cases, the Model, View and Controller roles <strong>may be played by the same object</strong>. Example: A scroll bar.</li>
<li>The View and Controller roles may be played by the same object when they are very tightly coupled. Example: A Menu.</li>
<li>In the general case, they can be played by three different objects. Example: An object-oriented design modeller.</li>
</ul>
<p>Shocking to anyone familiar with many popular MVC web frameworks. What about reusability and modularity, the goal of any software project worth its name? We must be able to re-use as many classes and components as possible for the next project, right?</p>
<p>Well, in reality, how much of that is true? Are you reusing the Views? Hardly, your next web site will look quite different. The Controllers? Nope, the routes, database access, etc, are different too. The Models then? Not if you are creating them according to the knowledge and terminology of users and Domain experts, then they will be different between customers. So what is reusable really?</p>
<p>It seems that we're only reusing CSS and HTML frameworks (occasionally!), some general-purpose libraries, and the GoF patterns that we still hang onto, <a target="_blank" href="https://stackoverflow.com/a/24664544/70894">but maybe shouldn't</a>. Those things are quite low-level. On a higher level, we want to create things (objects) relevant to the user, and they are very specific for each project. Also, striving for reusability before something is even used, smells of premature optimization.</p>
<p>All this means that we <em>can</em> couple MVC objects because they aren't on the actual level of being reusable. We should rather embrace the natural coupling to create small, efficient objects that are easy to understand, using terms based on the end user mental models. That's a step closer towards the Kay object-oriented vision for sure.</p>
<h2 id="heading-2-the-view-is-an-object">2. The View is an object!</h2>
<p>This follows from the previous point, <a target="_blank" href="https://groups.google.com/d/msg/object-composition/glaWzT7yJJY/WlDPe60pTnIJ">confirmed recently by Trygve</a>, but it's still worth emphasizing because the "web view" idea is so dominating: A "View" in web apps is usually considered a template, a code snippet rendered to HTML and displayed in the browser. Quite simple to use, but not as dynamic as web requirements are today. In early sites the template had to be populated with data, but there was no natural place for the data since there were no objects in sight. The template engine and framework handled this with key-value mappings initially, but after a while the <em>View Model</em> was invented. An object containing the data required for a view template to display correctly.</p>
<p>This is an example of something being invented because people are more interested in innovation than building on already laid foundations. Older stuff isn't as trendy, but it is solid and useful, like a timeless piece of equipment surviving through the ages.</p>
<p>If we instead apply the idea that a MVC View is an object, we can pass the relevant Model directly to that object, in the constructor for example, and we don't have to invent a concept like a View Model, that doesn't make sense to the user and/or complicates the program. State not related to the Model can be put directly in the View.</p>
<p>That solves the data part of the View, but what about functionality? Web apps today are quite interactive, but it's contrived to model interaction in a template snippet. Again, objects are a much better fit.</p>
<p>If you agree that the View should be an object, finding a MVC web framework that allows us to create Views as simple objects isn't that easy. <a target="_blank" href="https://mithril.js.org/">Mithril</a> is a shining exception however. It lets one focus on the system architecture instead of structure. It uses View Models, but they aren't a requirement. <a target="_blank" href="https://svelte.dev/">Svelte</a> is another example that embraces the natural coupling of MVC. Overall, the situation is improving with the "components" approach, which, in light of the above information, is really MVC in disguise.</p>
<h1 id="heading-more-details">More details</h1>
<p>Those were some urgent points. Now let's get into details, focusing on web development. The following list about the responsibilities of MVC objects is taken from the excellent book <a target="_blank" href="http://www.leansoftwarearchitecture.com/">Lean Architecture</a> by <a target="_blank" href="https://en.wikipedia.org/wiki/Jim_Coplien">James Coplien</a>, and I've added some comments.</p>
<h2 id="heading-model">Model</h2>
<p><strong>- Updates its data at the request of commands from the Controller</strong></p>
<p>This has been corrected since the book was published. Most user input comes in through the View, which will usually pass the input on to its Model, if the implementation allows. For modern web apps, objects are in memory in the browser, so it's no problem to let the View talk to the Model directly, since the View is coupled to the Model data anyway.</p>
<p><strong>- Notifies appropriate Views when its state changes</strong><br />
<strong>- Can be asked to register/de-register Views</strong></p>
<p>Progress has been made here for the web: Previously we've been stuck with tedious DOM manipulations, which made these two points seem necessary. In modern javascript frameworks like Mithril and React however, using a fast diff algorithm there is no need to register or tell specific Views that something has changed. The framework can be requested to redraw anytime, and the smallest possible DOM modification will be made. This gets rid of the Observer pattern, for example. Svelte takes it even further by compiling modifications instead of calculating them at runtime, eliminating the need for a diff algorithm.</p>
<h2 id="heading-view">View</h2>
<p><strong>- Presents a particular representation of the data to the user</strong><br />
<strong>- Can ask a Model for the current value of data it presents to the user</strong></p>
<p>This is quite simple now that we have a View object that either is passed a Model, or is the model itself. Just ask the Model for the data and display it appropriately.</p>
<p>Regarding the relationship between Models and Views, Reenskaug and Coplien agrees that <em>"a given view canonically has a single Model."</em> Reenskaug continues:</p>
<blockquote>
<p>Other Views can have other Models. The subview structure could reflect the model structure, or it may merely keep things together that belong together in the user's tasks.</p>
</blockquote>
<p><strong>- Can handle its own input</strong></p>
<p>In web applications, the View object is usually the point of user interaction, through <a target="_blank" href="https://en.wikipedia.org/wiki/DOM_events">DOM events</a>. Quoting Coplien here: </p>
<blockquote>
<p>A View can handle some of its own input (e.g., a text input field can handle all local text processing - backspaces, line kills, etc.) only to send the data to the Model when an ENTER key is pressed or a button is pushed - but that's kind of an optimization. The important is that the end users should feel as though they are interacting directly with the Model. That completes the link from the end user mind to the program and back.</p>
</blockquote>
<p>Many modern frameworks uses DOM events to automatically redraw the screen, since most user input results in some visual change. Again very useful to avoid patterns like Observer.</p>
<p><strong>- Together with the Controller, handles selection</strong></p>
<p>This one is interesting. From <a target="_blank" href="https://folk.universitetetioslo.no/trygver/2003/javazone-jaoo/MVC_pattern.pdf">MVC - Its Past and Present</a>, page 13:</p>
<blockquote>
<p>A user selects one or more objects in one of the Views. The selection should appear in all Views where the selected object is visible in order to maintain the object model illusion. [...] <em>Solution:</em> Let the selection be the responsibility of an object that knows all the relevant Views.</p>
</blockquote>
<p>The object in question is the Controller. This means that the Controller will contain a list available to the Views it manages. When the list changes, so will the Views displaying the selected objects. The selection input is passed on from the Views to the Controller.</p>
<h2 id="heading-controller">Controller</h2>
<p>In the original MVC, a Controller is mainly a View manager, with the following responsibilities:</p>
<p><strong>- Creates and manages Views</strong></p>
<p>When Views are objects, a common issue of how to organize "parent-child" or "between component" communication is much simplified. The Controller will instantiate the required views and make sure the correct Models are passed to them. An illuminating note from Reenskaug:</p>
<blockquote>
<p>Let a View play two roles: Let it be managed by its Controller in the usual manner and, at the same time, be (sub)Controller that manages a number of subviews.</p>
</blockquote>
<p><strong>- Handles selection across Views</strong><br /></p>
<p>See the last point in the View section above.</p>
<p><strong>- Passes commands to the Model</strong></p>
<p>This has also been corrected as of late. Since the Views talks directly to the Model when possible, it's not required to pass any commands through the Controller. It could be required if the Controller is the only way in the framework to send requests to the server, but that's also an implementation detail.</p>
<p>Commands are further discussed in <em>The original MVC reports</em>:</p>
<blockquote>
<p>A view should never know about user input, such as mouse operations and keystrokes. It should always be possible to write a method in a controller that sends messages to views which exactly reproduce any sequence of user commands.</p>
</blockquote>
<p>This has been confirmed by Reenskaug as implementation-specific, not a general rule. But even though the View can handle input in the browser, the last part is still a useful guideline when writing the public interface/API for the MVC objects. Do they reflect the way the user thinks about the system? And when someone wants to connect to your useful software with other software, will it be an intuitive process, or will it feel like a feature that was slapped on as an afterthought?</p>
<p><strong>- Handles commands that applies to the entire window</strong></p>
<p>Having an overarching Controller for the whole window already exists in many MVC frameworks. It's usually called a <em>Router</em>, and now when we allow it to be any combination of M, V and C, its usefulness increases.</p>
<h1 id="heading-the-user">The User</h1>
<p>I hope you can now see and better understand the composition of a system using MVC. Objects are playing the <em>roles</em> of M, V and C, containing other objects that exists and communicates according to the mental model of the User. To signify how important the last part is, Reenskaug and Coplien have <a target="_blank" href="https://www.artima.com/articles/the-dci-architecture-a-new-vision-of-object-oriented-programming">improved the name of the pattern</a> to <em>MVC-U</em>, finally including the <em>User</em>, the most important part of the system. It is, after all, not only for ourselves but mostly for others we code.</p>
<h1 id="heading-dci-the-mvc-complement">DCI - The MVC complement</h1>
<p>If you want to dive even deeper in system architecture: In languages today there isn't a natural place for functionality that involves a network of communicating objects. This is a topic that leads us to <a target="_blank" href="https://wikipedia.org/wiki/Data,_context_and_interaction">DCI</a> (Data, Context, Interaction), Reenskaug's next invention, co-authored by Coplien.</p>
<p>It is a new paradigm in system architecture, designed as a complement to MVC. Quoting from the introductory article <a target="_blank" href="https://www.artima.com/articles/the-dci-architecture-a-new-vision-of-object-oriented-programming">The DCI Architecture</a>:</p>
<blockquote>
<p>The MVC framework makes it possible for the user to reason about what the system is: the thinking part of the user cognitive model. But there is little in object orientation, and really nothing in MVC, that helps the developer capture doing in the code. The developer doesn't have a place where he or she can look to reason about end user behavioral requirements.</p>
</blockquote>
<p>As with any new paradigm, it cannot be understood with help of the previous one, so it takes some effort, but is really worth the time. It will make you a better programmer, guaranteed. The official DCI webpage at <a target="_blank" href="http://fulloo.info/">http://fulloo.info/</a> is simple but informative, has a thorough FAQ, and as I've spent 10 years on the DCI frontier, I'm also available for answering your questions. Enjoy!</p>
<h1 id="heading-a-final-note">A final note</h1>
<p>Reenskaug writes in a mail:</p>
<blockquote>
<p>It is important to realize that these reports reflect an idea and a particular implementation. They are not a final answer where every sentence can be taken as normative.</p>
</blockquote>
<p>A humble acknowledgement that things will be different in various systems, and in the future. If we strive towards simplicity and focus on the end user, the above ideas should in any case get us closer to a better way to write useful software, I'm quite certain of that!</p>
<p>Now when you know more about the human side of MVC, I hope it will become a great help in your future system design. Thanks for reading, and please let me know what you think!</p>
]]></content:encoded></item></channel></rss>