<?xml version="1.0"?>
<rss version="2.0">
<channel>
	<title>wissel.net Usability - Productivity - Business - The web - Singapore and Twins</title>
	<link>https://wissel.net/blog/stories.rss</link>
	<description>Thoughts, Insights and Opinions of Stephan H. Wissel. Topics included: Salesforce, Lotus Notes and Domino, IBM Websphere, NodeJS, JavaScript,  J2EE, .Net, Software Archtecture, Personcentric Development, Agile Software, SDLC, Singapore and my Twins</description>
	<language>en,de</language>
	<copyright>(C) 2003 - 2021 Stephan H. Wissel, All rights reserved</copyright>
	<pubdate>Mon, 18 May 2026 12:55:28 +0000</pubdate>
<item>
	<title>My AI coding workflow</title>
	<description>&lt;p&gt;There are &lt;a href="https://www.google.com/search?q=How+to+best+code+with+AI"&gt;infinite opinions&lt;/a&gt; available how to use AI in software development. I haven't formed one, so I'm documenting what, so far has worked for me. I distinguish between the ideation phase, which's output is a set of specs, issues, tickets, whatever you call it and the implementation phase, which's output is working code.&lt;/p&gt;
&lt;p&gt;The two phases run in sequence (doh), but not exclusive. E.g. while feature one and two run through implementation, feature three and four csn be in ideation. This post is about the implementation phase.&lt;/p&gt;
&lt;h2&gt;Chinese whispers (AI edition)&lt;/h2&gt;
&lt;p&gt;Based on the ideation phase I start with a set of github issues. They tend to be more than single sentence descriptions of an desired outcome. I launch multiple instances of Claude using &lt;code&gt;claude -w&lt;/code&gt;, to enable the &lt;a href="https://git-scm.com/docs/git-worktree"&gt;git worktree&lt;/a&gt; support. The exact number depends on your willingness to endure frequent context switching, I'm old so I stick to 2-4.&lt;/p&gt;
&lt;p&gt;In each instance I follow the same prompt: "&lt;em&gt;Plan and implement issue &lt;code&gt;#42&lt;/code&gt;, share your assumptions, ask clarifying questions, outline options and seek approvals&lt;/em&gt;"&lt;/p&gt;
&lt;p&gt;AI goes through a round of questions and challenges until we reach consensus and it implements the task at hand. Once done it creates a pull request. The creation of the pull request triggers code reviews. Currently that's &lt;a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review"&gt;GitHub Copilot review&lt;/a&gt; and &lt;a href="https://coderabbit.link/stephan-h-wissel"&gt;CodeRabbit AI&lt;/a&gt; (disclaimer: the link is a referral link). Both agents review the code, come to conclusions and share them in conversations in the pull request. What's interesting is that they both highlight different issues with limited overlap. I'm particularly fond of CodeRabbit's "nitpick" level of feedback.&lt;/p&gt;
&lt;p&gt;Once their review is completed, I let the PR merge, most of the time more that one from the various work trees. To close the feedback loop I then prompt: "&lt;em&gt;Visit all pull requests [timeframe] and look for unresolved conversations. Assess each claim for merit and decide if it is still valid. When no longer valid, post your justification as comment. When still valid, create a new issue with detailed explanation and options, so any developer can understand and act on it, then add a comment linking to the issue. In both cases, mark the conversation as resolved.&lt;/em&gt;"&lt;/p&gt;
&lt;p&gt;This approach strikes a balance between "let the agent do its thing" and "keep the development transparent". If your source of truth for development tasks isn't GitHub issues, you need to adjust my approach.&lt;/p&gt;
&lt;p&gt;&lt;img src="/blog/images/2026/CodingWorkflow.jpg" alt="Coding Workflow"&gt;&lt;/p&gt;
&lt;p&gt;All of this gets supported by &lt;code&gt;CLAUDE.md&lt;/code&gt;, MCP, Skills, Tools etc, but that's another story for another time.&lt;/p&gt;
&lt;p&gt;As usual YMMV&lt;/p&gt;</description>
	<link>2026/05/my-ai-coding-workflow.html</link>
	<author>Stephan H. Wissel</author>
	<guid>b2f58350-52a2-11f1-ab41-6f3251288c92</guid>
	<pubDate>08 May 2026</pubDate>

</item>
<item>
	<title>Cumulative data modeling (Part 2)</title>
	<description>&lt;p&gt;Continuing from &lt;a href="/blog/2026/01/cumulative-data-modeling-part1.html"&gt;Part 1&lt;/a&gt;: What if there is an alternative to a normalized &lt;a href="https://en.wikipedia.org/wiki/Relational_database"&gt;RDBMS&lt;/a&gt; storage approach, that better fits the process flow?&lt;/p&gt;
&lt;h2&gt;The routing slip data model&lt;/h2&gt;
&lt;p&gt;Instead of looking at the physical entities (Suppliers, Ingredients, Cakes, Mixes, Slices etc.), look at the process steps and treat the process steps as our physical entities that store participating data.&lt;/p&gt;
&lt;p&gt;&lt;img src="/blog/images/2026/CheeseCakeFactory2.jpg" alt="Cheesecake factory"&gt;&lt;/p&gt;
&lt;p&gt;The main difference to normalization is to treat data as process local. 1kg flour in the mixing state is a different datapoint from 1kg four in storage silo 3. Routing slips provide the transition. Key difference: Routing slips are &lt;strong&gt;accumulative&lt;/strong&gt;. So the routing slip in the package process contains all the routing slip data of previous process steps. From a database perspective that is a deliberate data duplication, raising blood pressure for data architects.&lt;/p&gt;</description>
	<link>2026/05/cumulative-data-modeling-part-2.html</link>
	<author>Stephan H. Wissel</author>
	<guid>679478c0-47cc-11f1-81c8-3f31675eb608</guid>
	<pubDate>04 May 2026</pubDate>

</item>
<item>
	<title>Standards are Standards until they are not</title>
	<description>&lt;p&gt;In my junior developer days, literally last century, a senior developer enlightened me: "&lt;em&gt;An open standard is only complete once its wording allows all participants to faithfully implement it to be absolutely incompatible to each other&lt;/em&gt;"&lt;/p&gt;
&lt;h2&gt;Ambiguity is king&lt;/h2&gt;
&lt;p&gt;Our modern software is build on a few standards which made progress at an unprecedented scale possible:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://home.unicode.org/"&gt;Unicode&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html"&gt;HTTP&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://html.spec.whatwg.org/"&gt;HTML&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol"&gt;SMTP&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://ecma-international.org/publications-and-standards/standards/ecma-262/"&gt;ECMA Script&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://www.json.org/json-en.html"&gt;JSON&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://openid.net/developers/how-connect-works/"&gt;OIDC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are plenty more. Each of them have rock solid parts (nobody questions a &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;) and parts of, let's call them disagreements. I'll pick on &lt;a href="https://www.jwt.io/"&gt;JWT&lt;/a&gt; as an example:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;agreed upon are the three parts: header, payload &amp;amp; signature&lt;/li&gt;
 &lt;li&gt;agreed upon ia that the payload is &lt;a href="https://www.json.org/json-en.html"&gt;JSON&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The actual content of the payload is already subject to interpretation.&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;&lt;a href="https://www.keycloak.org/"&gt;Keycloak&lt;/a&gt; stands out for its flexibility and steep learning curve&lt;/li&gt;
 &lt;li&gt;Refresh tokens are handled quite differently:&lt;/li&gt;
 &lt;li&gt;Keycloak issues refresh tokens as JWT, others as opaque strings&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://www.okta.com/"&gt;Okta&lt;/a&gt; doesn't issue refresh tokens for &lt;a href="https://oauth.net/2/pkce/"&gt;PKCE&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;EntraID returns the Application ID in the &lt;code&gt;aud&lt;/code&gt; field instead of an array of target servers, the &lt;code&gt;iss&lt;/code&gt; parameter does't match the &lt;code&gt;.well-known&lt;/code&gt; info&lt;/li&gt;
 &lt;li&gt;&lt;a href="https://www.pingidentity.com/"&gt;PING Identity&lt;/a&gt; sends &lt;code&gt;scopes&lt;/code&gt; not as space separated list, but as JSON array&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And all implement "the standard"&lt;/p&gt;</description>
	<link>2026/04/standards-are-standards-until-they-are-not.html</link>
	<author>Stephan H. Wissel</author>
	<guid>1000a4b0-36ee-11f1-b4fa-a100177ccac1</guid>
	<pubDate>13 April 2026</pubDate>

</item>
<item>
	<title>AI generated blog article hero graphics</title>
	<description>&lt;p&gt;I always liked blog entries that feature an eye catching image at the top of an article, except when on a slow connection ?_(?)_/?. So I was wondering how I could add them to my &amp;gt; 1000 blog articles. Adjusting the &lt;a href="https://mustache.github.io/"&gt;mustache&lt;/a&gt; template was fast and easy. My first thought was to create one image per category and reuse that. But that would have been too easy.&lt;/p&gt;
&lt;h2&gt;With a little help of my friends&lt;/h2&gt;
&lt;p&gt;I wanted the images vary in style and have some level of relation to the article. So I asked &lt;a href="https://claude.ai/"&gt;Claude&lt;/a&gt; for options. It suggested to use the &lt;a href="https://platform.claude.com/"&gt;Anthropic API&lt;/a&gt; to generate a picture prompt out of the content of each blog article and submit it to &lt;a href="https://openai.com/index/dall-e-3/"&gt;DallE3&lt;/a&gt; for image creation.&lt;/p&gt;
&lt;p&gt;I liked the idea, so I asked for implementation. Claude generated a JS file that takes the file name as input.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;node --env-file=.env generate-cover.mjs ../path/to/blog/article.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole script is &lt;a href="https://gist.github.com/Stwissel/f203b3703043181bd174935660c2546b"&gt;available here&lt;/a&gt; for your entertainment. To make it more fun: when you click on one of the header images, it will open the prompt used to generate it.&lt;/p&gt;
&lt;p&gt;The prompt to create the prompt:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-txt"&gt;You are an expert at writing prompts for DALL?E 3 image generation.

Read the blog post below and write a single, vivid image prompt (max 200 words) that:
- Captures the post's core theme or mood
- Works as an eye-catching blog cover image
- for the style cycle through the following styles at random:
  - Photorealistic
  - Tasteful illustrative
  - Cyberpunk
  - Vintage
  - Minimalistic
  - Comic book
- Contains NO text, letters, or words in the image
- Avoids clich?s (no light bulbs, no handshakes, no generic office scenes)

Reply with ONLY the prompt ? no preamble, no explanation.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;</description>
	<link>2026/03/ai-generated-blog-article-hero-graphics.html</link>
	<author>Stephan H. Wissel</author>
	<guid>5ce04250-1a9b-11f1-bc7d-a5078e35cf24</guid>
	<pubDate>08 March 2026</pubDate>

</item>
<item>
	<title>VSCode launch settings for DominoJNX and HCL Notes 14.5</title>
	<description>&lt;p&gt;I'm not the only one who &lt;a href="https://youtu.be/d8WEzjDx4-E?si=ljSEAefAOKvrcrXr"&gt;doesn't like Windows&lt;/a&gt;, which is a problem when you depend on Domino Designer. Luckily there is Java and &lt;a href="https://opensource.hcltechsw.com/domino-jnx/"&gt;DominoJNX&lt;/a&gt;. A decade ago I would &lt;a href="https://stackoverflow.com/questions/39470027/running-an-ibm-notes-java-application-on-os-x-el-capitan-throws-unsatisfiedlinke"&gt;use Eclipse&lt;/a&gt; on an Intel Mac (and Java8). Much has changed. Java is now 21, the Mac is based on Arm and my primary IDE is &lt;a href="https://code.visualstudio.com/"&gt;VSCode&lt;/a&gt;. Here is my setup.&lt;/p&gt;
&lt;h2&gt;x86 vs. arm&lt;/h2&gt;
&lt;p&gt;To run a Java app against the Notes 14.5 client there are several steps required:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;An installed and running Notes 14.5 client (duh)&lt;/li&gt;
 &lt;li&gt;MacOS Rosetta installed&lt;/li&gt;
 &lt;li&gt;a 64Bit x86 JDK (won't work with an ARM JDK)&lt;/li&gt;
 &lt;li&gt;A &lt;code&gt;launch.json&lt;/code&gt; file in &lt;code&gt;.vscode&lt;/code&gt; of your project&lt;/li&gt;
&lt;/ul&gt;</description>
	<link>2026/02/vscode-launch-settings-for-dominojnx-and-hcl-notes.html</link>
	<author>Stephan H. Wissel</author>
	<guid>cc1e4050-011d-11f1-a041-fbe6b9e0116f</guid>
	<pubDate>04 February 2026</pubDate>

</item>
<item>
	<title>Cumulative data modeling (Part 1)</title>
	<description>&lt;p&gt;The going approach to model data is drawing your &lt;a href="https://mermaid.ai/open-source/syntax/classDiagram.html"&gt;ER Diagram&lt;/a&gt; and map it to your persistence, be it &lt;a href="https://www.geeksforgeeks.org/dbms/normal-forms-in-dbms/"&gt;normalized&lt;/a&gt; in a &lt;a href="https://en.wikipedia.org/wiki/Relational_database"&gt;RDBMS&lt;/a&gt; or an entry in a &lt;a href="https://en.wikipedia.org/wiki/NoSQL"&gt;NoSQL&lt;/a&gt; store.&lt;/p&gt;
&lt;h2&gt;A slice of cake&lt;/h2&gt;
&lt;p&gt;Walking through the use case of a cheese cake factory, slightly simplified. All process steps happen in batches, but batch sizes are not synchronized. E.g. dough mixing produces 0.8 - 1.4 times the baking capacity.&lt;/p&gt;
&lt;p&gt;&lt;img src="/blog/images/2026/CheeseCakeFactory.jpg" alt="Cheesecake factory"&gt;&lt;/p&gt;
&lt;p&gt;The corresponding data model would look like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/blog/images/2026/CheeseModel800.png" alt="Cheesecake data model"&gt;&lt;/p&gt;
&lt;p&gt;Now imagine a customer wants to know if the slice is made with cheese from "happy cows", which is a trade certification some of the suppliers have. A simple SQL solves it:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT
    s.slice_id,
    ROUND(
        (SUM(CASE WHEN sup.HappyCowCertified = TRUE THEN mi.quantity_used ELSE 0 END) /
         SUM(mi.quantity_used)) * 100,
        2
    ) AS happy_cow_percentage
FROM Slices s
JOIN Baked_Cakes bc ON s.cake_id = bc.cake_id
JOIN Mix_Ingredients mi ON (
    mi.mix_id = bc.dough_mix_id OR
    mi.mix_id = bc.filling_mix_id OR
    mi.mix_id = bc.topping_mix_id
)
JOIN Ingredients i ON mi.ingredient_id = i.ingredient_id
JOIN Ingredient_Batches ib ON mi.batch_id = ib.batch_id
JOIN Suppliers sup ON ib.supplier_id = sup.supplier_id
WHERE s.slice_id = 'someSliceId'
  AND i.name = 'Cream Cheese'
GROUP BY s.slice_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another day another challenge: a batch from a supplier was sub standard and you need to recall all slices made with it. SQL to the rescue again:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sql"&gt;SELECT DISTINCT
    s.slice_id,
    s.storage_location
FROM Slices s
JOIN Baked_Cakes bc ON s.cake_id = bc.cake_id
JOIN Mix_Ingredients mi ON (
    mi.mix_id = bc.dough_mix_id OR
    mi.mix_id = bc.filling_mix_id OR
    mi.mix_id = bc.topping_mix_id
)
WHERE mi.batch_id = 'someBadBatchId'
ORDER BY s.slice_id;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As long as as bakery control system has this baked in (pun intended) or you have your residential SQL jockey on speed dial, all is well (keep in mind: the real data model is way more complex), but there might be a better way, slightly unconventional. Continue reading &lt;a href="/blog/2026/05/cumulative-data-modeling-part2.html"&gt;part 2&lt;/a&gt;.&lt;/p&gt;</description>
	<link>2026/01/cumulative-data-modeling-part1.html</link>
	<author>Stephan H. Wissel</author>
	<guid>557c13d0-f837-11f0-aa29-0b135a78174a</guid>
	<pubDate>23 January 2026</pubDate>

</item>
<item>
	<title>Running your SPA on Cloudflare</title>
	<description>&lt;p&gt;Your application is ready, you want to prepare for the incoming storm of users, so you separate your SPI server from your UI. To host the UI &lt;a href="https://www.cloudflare.com/"&gt;Cloudflare&lt;/a&gt; is chosen, while the API server sits in its data centre. There are a few steps to be had, which are simple, just the AI was acting cute, so I write it down.&lt;/p&gt;
&lt;p&gt;&lt;img src="/blog/images/2026/CloudflareSPA.png" alt="Cloudflare hosted SPA"&gt;&lt;/p&gt;
&lt;h2&gt;Workers &amp;amp; Pages&lt;/h2&gt;
&lt;p&gt;We shall update our single page application whenever new contenet gets merged into &lt;code&gt;main&lt;/code&gt;. All calls to &lt;code&gt;/api/*&lt;/code&gt; need to get routed to the API server. These are the steps that wrok. Nota bene: if you want to push from your local machine, the steps are different and a story for another time.&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;In Cloudflare head to &lt;code&gt;Build&lt;/code&gt;, &lt;code&gt;Compute &amp;amp; AI&lt;/code&gt;, &lt;code&gt;Workers &amp;amp; Pages&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;Don't get distracted by the blue "Create application" button, you want to click above it and select &lt;code&gt;+ Add&lt;/code&gt; and then Pages (don't select "Worker")&lt;/li&gt;
 &lt;li&gt;Select "Import an existing Git repository" and select your repo (You need the &lt;a href="https://developers.cloudflare.com/cloudflare-one/integrations/cloud-and-saas/github/"&gt;GitHub integration&lt;/a&gt; for that).&lt;/li&gt;
 &lt;li&gt;configure your build settings, for a &lt;a href="https://vite.dev/guide/"&gt;viteJS&lt;/a&gt; applicationa, the build command is &lt;code&gt;npm run build&lt;/code&gt; and the output directory is &lt;code&gt;dist&lt;/code&gt;. Save it and you are good to go.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next step is to configure "Custom domains", so &lt;code&gt;app.example.com&lt;/code&gt; points to your page. Follow the UI, it is almost automagic when Cloudflare is your DNS provider&lt;/p&gt;
&lt;h2&gt;Redirecting &lt;code&gt;/api&lt;/code&gt; to the backend&lt;/h2&gt;
&lt;p&gt;We want Cloudflare to &lt;strong&gt;proxy&lt;/strong&gt; it, not to &lt;code&gt;30x&lt;/code&gt; redirect. A client wouldn't know that the request gets forwarded. This will allow to harden the API (e.g. drop all request not coming from Cloudflare) later on. Also you don't have to deal with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS"&gt;CORS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Search, with and without AI, suggested solutions revolving around files:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;create &lt;code&gt;_routes.json&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;code &lt;code&gt;_worker.js&lt;/code&gt;&lt;/li&gt;
 &lt;li&gt;use an elaborate wrangler setups.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those all have one thing in common: &lt;strong&gt;they didn't work&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What worked: create a folder &lt;code&gt;functions&lt;/code&gt; at the root of your repository. Inside add a file &lt;code&gt;[[path]].js&lt;/code&gt; - yes, that's two pairs of square brackets and th letters p a t h. It's the catch all for functions. Inside you add:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;export async function onRequest(context) {
  const { request } = context;
  const url = new URL(request.url); // Test endpoint

  if (url.pathname === '/helloworld') {
    return new Response('Workers of the world unite!', {
      headers: { 'Content-Type': 'text/plain' }
    });
  } // Proxy API requests

  if (url.pathname.startsWith('/api/')) {
    url.hostname = 'api.example.com';
    url.port = '4443';
    url.protocol = 'https:';

    return fetch(url, request);
  }

  return context.next();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Update&lt;/h3&gt;
&lt;p&gt;Instead of creating &lt;code&gt;functions/[[path]].js&lt;/code&gt;, create &lt;code&gt;functions/api/[[path]].js&lt;/code&gt; and simplify the function:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;export async function onRequest(context) {
  const { request } = context;
  const url = new URL(request.url);

  url.hostname = 'api.example.com';
  url.port = '4443';
  url.protocol = 'https:';

  return fetch(url, request);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hardening the setup is a story for another time.&lt;/p&gt;
&lt;p&gt;As usual YMMV&lt;/p&gt;</description>
	<link>2026/01/running-your-spa-on-cloudflare.html</link>
	<author>Stephan H. Wissel</author>
	<guid>9d69e750-f62d-11f0-b02b-a7d7e8629bc5</guid>
	<pubDate>20 January 2026</pubDate>

</item>
<item>
	<title>Unless you have a Google size API problem use curl</title>
	<description>&lt;p&gt;A few days ago &lt;a href="https://www.linkedin.com/posts/notessensei_api-curl-activity-7409921612931620864-uJXO"&gt;I posted on LinkedIn&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;"&lt;em&gt;Unless you have a Google/Meta/Antropic size &lt;a href="https://en.wikipedia.org/wiki/API"&gt;API&lt;/a&gt; problem, I have a simple rule/insight:&lt;br&gt;
   Don?t create an api you can?t interact with using &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt;&lt;/em&gt;".&lt;/p&gt;
&lt;p&gt;I've been asked to elaborate.&lt;/p&gt;
&lt;h3&gt;The tool and the task&lt;/h3&gt;
&lt;p&gt;Firstly a remark about the tool, &lt;a href="https://curl.se/"&gt;curl&lt;/a&gt;. It is an incredibly powerful tool with super flexible use cases. Just look at the list of supported protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS, WSS.&lt;/p&gt;
&lt;p&gt;Understanding, even mastering it, is a must have for any developer (or admin for that matter).Want to fetch data unencumbered by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS"&gt;CORS&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP"&gt;CSP&lt;/a&gt; - curl is your friend.&lt;/p&gt;
&lt;p&gt;You can read the sentence above also this way: &lt;strong&gt;&lt;em&gt;Don't design an API if you. haven't mastered curl&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Picking an API architecture is a big decision and there are quite many to pick from:&lt;/p&gt;
&lt;p&gt;Here's the markdown source code:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
  &lt;tr&gt;
   &lt;th&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/th&gt;
   &lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
  &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer"&gt;REST&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Uses HTTP methods to access and change resources through URLs. Robust &lt;a href="https://www.openapis.org/"&gt;well supported&lt;/a&gt; architecture with mature tooling. Easy to discoverm used for Web services, mobile apps or public APIs. Not suitable for real-time bidirectional communication or data transfer minimalism. Typically uses JSON payloads, previously XML, but will cater any data. Complex queries need to be handled in application logic. Really needs &lt;a href="https://httpwg.org/http-extensions/draft-ietf-httpbis-safe-method-w-body.html"&gt;HTTP QUERY&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Remote_procedure_call"&gt;RPC&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;While REST is resource oriented, RPC is &lt;a href="https://www.geeksforgeeks.org/system-design/difference-between-rest-api-and-rpc-api/"&gt;action oriented&lt;/a&gt; and is not limited to the HTTP(s) protocol. Typically you have a single endpoint where the posted payload determines the action. Examples are &lt;a href="https://en.wikipedia.org/wiki/SOAP"&gt;SOAP&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/JSON_Meta_Application_Protocol"&gt;JMAP&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture"&gt;CORBA&lt;/a&gt; or propriarty protcols like in use with &lt;a href="https://react.dev/reference/rsc/server-components"&gt;React Server Components&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://graphql.org/"&gt;GraphQL&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Client requests exactly the data it needs using a query language, used in apps needing flexible data fetching, reducing over-fetching. Since the Query is clientside complexity is shifted to the client and security can be a nightmare. Sucks for caching or file uploads. &lt;a href="https://developer.microsoft.com/en-us/graph/graph-explorer"&gt;Microsoft is a fan&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/SOAP"&gt;SOAP&lt;/a&gt;&lt;/strong&gt; (Simple Object Access Protocol)&lt;/td&gt;
   &lt;td&gt;XML-based protocol with strict standards and built-in error handling. Unparalled accurate through &lt;a href="https://en.wikipedia.org/wiki/Web_Services_Description_Language"&gt;WSDL&lt;/a&gt; definitions. Works well between unrelated parties, e.g. &lt;a href="https://groups.oasis-open.org/communities/tc-community-home2?CommunityKey=eb27e05e-9bf5-4821-b1ed-018dc7d3f258"&gt;ebXML&lt;/a&gt;, heavy protocol, not popular in web applications anymore (despite giving &lt;a href="https://en.wikipedia.org/wiki/Ajax_(programming)"&gt;AJAX&lt;/a&gt; its name)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://grpc.io/"&gt;gRPC&lt;/a&gt;&lt;/strong&gt; (Google Remote Procedure Call)&lt;/td&gt;
   &lt;td&gt;High-performance framework using &lt;a href="https://en.wikipedia.org/wiki/Protocol_Buffers"&gt;Protocol Buffers&lt;/a&gt;. The data definitions are not transfered, like in XML or JSON, so data transfer is minimized, can transfer bucket loads of data. When you run trillions of requests, like Google, every bit counts. Severe limitation on discoverability, language support and tooling. Best used server to server&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API"&gt;WebSocket&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Two-way communication channel over a single TCP connection used for Chat apps, live updates, real-time gaming, scaling is not for the faint of heart&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Webhook"&gt;Webhook&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Server sends automatic HTTP callbacks when events occur. Basically two way REST. Elegant and hard to test unless you can control the callback destination. Popular for payment notifications, automated workflows, CI/CD pipelines. Has special needs to make it traceable, no guaranteed delivery or real-time two-way communication&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Message_queue"&gt;MQ&lt;/a&gt;&lt;/strong&gt; (Message Queue) or &lt;strong&gt;&lt;a href="https://mqtt.org/"&gt;MQTT&lt;/a&gt;&lt;/strong&gt; (Message Queuing Telemetry Transport)&lt;/td&gt;
   &lt;td&gt;Publish/subscribe messaging protocols, MQTT is suitable for low-bandwidth networks. Asynchronus processing of requests for loosely coupled systems. Very flexible but its store and forward principles require careful planning, especially around security and replay (keyword &lt;a href="https://en.wikipedia.org/wiki/Idempotence"&gt;Idempotency&lt;/a&gt;)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
   &lt;td&gt;&lt;strong&gt;&lt;a href="https://kafka.apache.org/"&gt;Kafka&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
   &lt;td&gt;Distributed streaming platform that handles high-volume data streams. In laymen's terms: a queue like MQ, with added transformation capabilities, persisten queue storage, suitable for really big volumes of data (just ask &lt;a href="https://www.uber.com/en-PH/blog/kafka-tiered-storage/"&gt;Uber&lt;/a&gt; about it). Fun to use, not fun to run (strongly advise &lt;a href="https://www.google.com/search?q=hosted+kafka"&gt;hosted Kafka&lt;/a&gt;). Kafka is the big gun, it can solve your problem, in a lot of case like a shipping container can deliver a single pizza&lt;/td&gt;
  &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Looking at the options (I'm sure there are more), you can see which ones can be covered by curl: REST, some RPC (when HTTP or SMTP based), GraphQL, SOAP, WebSocket, Webhooks (only to initialize). Eventually Kafka, when its http interface is active, but that's just http based RPC.&lt;/p&gt;
&lt;p&gt;Compare the "Command Line Pain"? when using curl to model a request (or process the result with &lt;a href="https://jqlang.org/"&gt;jq&lt;/a&gt;). This gives you a data point you can add to your considerations which API to implement. It shouldn't be the only one: complexity, environment, data volumes, security etc. are equally important. The curl exercise helps to keep an eye on the tendency to overengineer.&lt;/p&gt;
&lt;p&gt;As usual YMMV&lt;/p&gt;</description>
	<link>2025/12/unless-you-have-a-google-size-api-problem-use-curl.html</link>
	<author>Stephan H. Wissel</author>
	<guid>ed2a6eb0-e618-11f0-8cc7-f32b674a3c60</guid>
	<pubDate>31 December 2025</pubDate>

</item>
<item>
	<title>Higher Level Custom Events</title>
	<description>&lt;p&gt;A common occurence in web applications are button bars that execute various commands. So I was thinking of a pattern how to make this flexible and repeatable.&lt;/p&gt;
&lt;h3&gt;Separate Event and Actions&lt;/h3&gt;
&lt;p&gt;The typical setup we find is like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const btn = document.querySelector('#myButton');
btn.addEventListener('click', (event) =&amp;gt; {
  event.preventPropagation();
  // Business logic goes here
  doStuff();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This ties the logic to a low-level event of a low level component. To increase the usefulness we need to move from "mechanical" events to business level events, kind of.&lt;/p&gt;
&lt;p&gt;An anology: imagine a fastfood kitchen. The cook gets orders "2 burgers, large fries" that don't contain: customer tapped on the order kiosk, drive thru order or delivery request. This information iss handled by the front-desk.&lt;/p&gt;
&lt;p&gt;Similarly we can approach the button event. Instead tying our business logic to the click event, we tie it to a higher level event.&lt;/p&gt;
&lt;p&gt;&lt;img src="/blog/images/2025/ButtonEvents.jpg" alt="Components send events"&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;const btn = document.querySelector('#myButton');
btn.addEventListener('click', (event) =&amp;gt; {
  window.dispatchEvent(new CustomEvent('update-request', { detail: { button: btn }, bubbles: true, composed: true }));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is oblivious where the business logic happens, while the logic still knows where it came from by inspecting the detail object. Decoupling using custom events makes testing easier.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;window.addEventListener('update-request', updateStuff);
const updateStuff = async (event) =&amp;gt; {
  await doStuff;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far, so good. In the &lt;a href="#"&gt;next installment&lt;/a&gt; I'll tie it together with a custom component.&lt;/p&gt;</description>
	<link>2025/11/higher-level-custom-events.html</link>
	<author>Stephan H. Wissel</author>
	<guid>979ef3a0-c087-11f0-abd4-772f20901b91</guid>
	<pubDate>13 November 2025</pubDate>

</item>
<item>
	<title>A flexible filter for collections or maps</title>
	<description>&lt;p&gt;Building a proxy for http requests I came across an interesting generic problem: filter or mutate a map but only for the occurence of some given set of keys.&lt;/p&gt;
&lt;h3&gt;That's a lot of &lt;code&gt;if - else - switch&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The going approach is to have a specialized method with a switch:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-java"&gt;  Map&amp;lt;String, String&amp;gt; transformMap(Map&amp;lt;String, String&amp;gt; input, String joker) {
    Map&amp;lt;String, String&amp;gt; result = new HashMap&amp;lt;&amp;gt;();

    input.entrySet().forEach(entry -&amp;gt; {
      String key = entry.getKey();
      String value = entry.getValue();
      switch (key) {
        case "a":
          result.put(key, "alpha");
          break;
        case "b":
          result.put(key, "beta");
          break;
        case "j":
          result.put(key, joker + "!");
        default:
          result.put(key, value);
      }
    });

    return result;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gets big and hard to maintain fast, so I was thinking of a more neutral approach entertaining &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/Function.html"&gt;Function&lt;/a&gt; and &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Optional.html"&gt;Optional&lt;/a&gt;, hear me out.&lt;/p&gt;</description>
	<link>2025/10/a-flexible-filter-for-collections-or-maps.html</link>
	<author>Stephan H. Wissel</author>
	<guid>8e33a610-a13a-11f0-a492-855ef152a38b</guid>
	<pubDate>04 October 2025</pubDate>

</item>

</channel>
</rss>
