<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Domain-Modeling on Brian Faust</title><link>https://wimdy.works/tags/domain-modeling/</link><description>Recent content in Domain-Modeling on Brian Faust</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://wimdy.works/tags/domain-modeling/rss.xml" rel="self" type="application/rss+xml"/><item><title>One Parcel Doesn't Have One Pricing Weight</title><link>https://wimdy.works/thoughts/one-parcel-doesnt-have-one-pricing-weight/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/one-parcel-doesnt-have-one-pricing-weight/</guid><description>&lt;p&gt;A parcel weighs two kilograms. Its dimensions make its volumetric weight
4.8 kilograms. One service prices the larger measurement, rounded up to a
whole kilogram; another prices actual weight. The same parcel gives those
services pricing weights of five and two kilograms.&lt;/p&gt;
&lt;p&gt;Calculating a weight once and sharing it across both candidates would save
work. It would also calculate the wrong thing. The reusable fact is the
parcel&amp;rsquo;s geometry, not whichever service&amp;rsquo;s policy happened to run first.&lt;/p&gt;</description></item><item><title>The Engine Prices Parcels, Not Copies</title><link>https://wimdy.works/thoughts/the-engine-prices-parcels-not-copies/</link><pubDate>Sun, 01 Mar 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/the-engine-prices-parcels-not-copies/</guid><description>&lt;p&gt;I dropped the package-copy shortcut during the rating engine&amp;rsquo;s development.
If a shipment contained ten parcels, I wanted ten parcel records, not one
record with &lt;code&gt;copies: 10&lt;/code&gt;. Count-based pricing stayed. The alternative way of
describing the things being priced didn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;This was an earlier input-model decision, not another change made during the
Go port. It matters to the rest of the engine because parcels acquire
references: a transport leg carries particular parcels, an item allocation
belongs to a particular parcel, and a package-scoped rule needs that parcel&amp;rsquo;s
facts. A multiplier doesn&amp;rsquo;t answer those questions without more conventions.&lt;/p&gt;</description></item><item><title>Not Every Pricing Rule Belongs in a Formula</title><link>https://wimdy.works/thoughts/not-every-pricing-rule-belongs-in-a-formula/</link><pubDate>Sun, 01 Feb 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/not-every-pricing-rule-belongs-in-a-formula/</guid><description>&lt;p&gt;The first parcel costs EUR 20. Parcels two through ten cost EUR 5 each.
After that, each additional parcel costs EUR 3. Twelve parcels should cost
&lt;code&gt;20 + 9 × 5 + 2 × 3 = 71&lt;/code&gt; euros. There is nothing particularly exotic about
that tariff. Turning it into three small programs still creates more work
than the arithmetic suggests.&lt;/p&gt;
&lt;p&gt;My first pricing model could express those programs. It knew about package
scope, conditions, calculated amounts, and ordered adjustments. The problem
wasn&amp;rsquo;t an inability to calculate the right price. It was that ordinary tariff
structure kept arriving as another expression for the system to interpret.
That affected editing, validation, and eventually execution.&lt;/p&gt;</description></item><item><title>How a Shipping Quote Gets Its Price</title><link>https://wimdy.works/thoughts/a-shipping-price-is-a-calculation-not-a-lookup/</link><pubDate>Thu, 01 Jan 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-shipping-price-is-a-calculation-not-a-lookup/</guid><description>&lt;p&gt;A parcel weighs two kilograms. The tariff has a price for two kilograms.
Returning that price sounds reasonable. It can also be wrong before the
lookup finishes: the tariff may charge for volume, the account may have a
negotiated transport rate, and handling may apply to each parcel rather than
once to the shipment.&lt;/p&gt;
&lt;p&gt;I built a private logistics rating engine around those interactions. It
stayed private rather than becoming a commercial product. What made it
interesting was that most of the difficulty came from pricing itself. I
could work on the model without first accommodating years of
unrelated business exceptions. There was still plenty to get wrong.&lt;/p&gt;</description></item><item><title>A Primitive Can Hide a Domain Decision</title><link>https://wimdy.works/thoughts/a-primitive-can-hide-a-domain-decision/</link><pubDate>Wed, 01 Apr 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-primitive-can-hide-a-domain-decision/</guid><description>&lt;p&gt;Uppercasing a country code looks like normalization:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;mb_strtoupper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;It makes &lt;code&gt;fi&lt;/code&gt; and &lt;code&gt;FI&lt;/code&gt; equal. It also turns &lt;code&gt;finland&lt;/code&gt; into &lt;code&gt;FINLAND&lt;/code&gt; and leaves
&lt;code&gt;FIN&lt;/code&gt; looking plausible.&lt;/p&gt;
&lt;p&gt;The string is tidier, not safer. The function has changed its shape without
answering the domain question: is this a supported country identity in the
representation this boundary promises? A primitive becomes expensive when
every caller must remember that answer. The reason to introduce a domain type
isn&amp;rsquo;t that strings are unfashionable. It is that the scalar no longer carries
enough of the contract.&lt;/p&gt;</description></item><item><title>Time Zones Are Part of the Data Model</title><link>https://wimdy.works/thoughts/time-zones-are-part-of-the-data-model/</link><pubDate>Thu, 01 Jun 2017 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/time-zones-are-part-of-the-data-model/</guid><description>&lt;p&gt;One metering path assigns an event to a billing month after converting its
occurrence time into the account&amp;rsquo;s timezone. The same path derives its daily
usage key directly from the occurrence timestamp without that conversion.&lt;/p&gt;
&lt;p&gt;Near midnight, those two fields can disagree:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;event instant: May 31, 21:30 UTC
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;local business time: June 1, 00:30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;billing period: June
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stored usage date: May 31&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Each expression looks like an ordinary date calculation. They model different
things, and the defect stays invisible until the difference crosses a day or
month boundary.&lt;/p&gt;</description></item><item><title>Null Is a Domain Decision</title><link>https://wimdy.works/thoughts/null-is-a-domain-decision/</link><pubDate>Sat, 01 Apr 2017 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/null-is-a-domain-decision/</guid><description>&lt;p&gt;We added four nullable fields to preserve a pricing snapshot: net amount, tax
rate, tax amount, and gross amount. Each field could now be absent. That did
not make each field independently optional.&lt;/p&gt;
&lt;p&gt;For older records, all four nulls meant one thing: this record predates the
snapshot and does not contain captured pricing evidence. For new records, all
four values had to arrive together. A row containing gross and net but no tax
rate was not “partly known.” It was invalid.&lt;/p&gt;</description></item></channel></rss>