<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://javahippie.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://javahippie.net/" rel="alternate" type="text/html" /><updated>2026-03-29T10:30:18+00:00</updated><id>https://javahippie.net/feed.xml</id><title type="html">Tim Zöller</title><subtitle>The Java Hippie</subtitle><entry><title type="html">The best 6 strategies to support agentic programming</title><link href="https://javahippie.net/programming/llm/2026/03/29/the-best-6-strategies-to-support-agentic-programming.html" rel="alternate" type="text/html" title="The best 6 strategies to support agentic programming" /><published>2026-03-29T10:20:00+00:00</published><updated>2026-03-29T10:20:00+00:00</updated><id>https://javahippie.net/programming/llm/2026/03/29/the-best-6-strategies-to-support-agentic-programming</id><content type="html" xml:base="https://javahippie.net/programming/llm/2026/03/29/the-best-6-strategies-to-support-agentic-programming.html">&lt;p&gt;Agentic programming (“vibe coding”) is everywhere. In this article I’d like to share my top 6 strategies to support
LLM agents in writing, building and deploying good, structured and reliable code:&lt;/p&gt;

&lt;h2 id=&quot;1-provide-a-readmemd&quot;&gt;1. Provide a README.md&lt;/h2&gt;
&lt;p&gt;Your agent’s memory is short, and sometimes you onboard new LLMs from new vendors who don’t already have a working memory
of your code. Provide a comprehensive README.md in your code, so they can pick it up and gain key insights into your
codebase:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;What is the purpose of this code?&lt;/li&gt;
  &lt;li&gt;Why was the technology stack selected?&lt;/li&gt;
  &lt;li&gt;How can the architecture be described?&lt;/li&gt;
  &lt;li&gt;How is the code structured and organized?&lt;/li&gt;
  &lt;li&gt;What are the code styles?&lt;/li&gt;
  &lt;li&gt;What are commit message conventions?&lt;/li&gt;
  &lt;li&gt;How can the application be built and tested?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This always provides a good starting point for agents and will save a lot of time and tokens, because they don’t have
to read the whole codebase when starting work.&lt;/p&gt;

&lt;h2 id=&quot;2-provide-and-maintain-comprehensive-documentation&quot;&gt;2. Provide and maintain comprehensive documentation&lt;/h2&gt;
&lt;p&gt;While the README.md is a good start, you absolutely need to provide well-written documentation to your LLM. This means both inline in the
code and as standalone documents. Describe the architecture, its intent and the decisions that led to its design. Describe the value your
software provides, how it interacts with users, and what the business requirements are. Written text is a good
way to start, but you should support it with graphics and schematics. LLMs can read these and get a good understanding of the
technical aspects and requirements for your application. If your coding agents need to make assumptions about your requirements
or your architecture, they might develop features and functionality you don’t need or which actually harm your application.&lt;/p&gt;

&lt;h2 id=&quot;3-modularize-your-codebase-if-possible&quot;&gt;3. Modularize your codebase, if possible&lt;/h2&gt;
&lt;p&gt;While the context window for LLM is becoming bigger and bigger, they still can not see the context of the whole application
at once, most of the time. Working on smaller, mostly self-contained modules with clear purpose and interfaces keeps this
“mental load” down and prevents changes made by the agent from bleeding into other parts of your application. Bonus points
if visibility of internal parts is low, so internal refactorings can happen without breaking access by other modules.&lt;/p&gt;

&lt;h2 id=&quot;4-tests-tests-tests&quot;&gt;4. Tests, tests, tests&lt;/h2&gt;
&lt;p&gt;Good tests act as your spec. They describe the intended behavior of your application. In a perfect world, they align with the 
requirements in your documentation. As already described above, the LLM cannot see the whole context of your application and
might break existing functionality when implementing new features. By running unit and integration tests, the agents get instant feedback about
introduced bugs and can adjust their approach to make sure they don’t break anything. They are still 
unreliable and prone to making mistakes, after all.&lt;/p&gt;

&lt;h2 id=&quot;5-have-a-reliable-build-pipeline&quot;&gt;5. Have a reliable build pipeline&lt;/h2&gt;
&lt;p&gt;Your agents are expensive, they shouldn’t be wasting time running deployments of your application manually. Mistakes in
this process are expensive and can cause downtime, too, so why not automate deployments? On new commits, run the entire
test suite, do a static code analysis for common mistakes, analyze for known security issues, package the application
in a standardized way and deploy it automatically to save tokens and avoid hallucinations while interacting with your
infrastructure.&lt;/p&gt;

&lt;h2 id=&quot;6-extensive-logging-and-tracing&quot;&gt;6. Extensive logging and tracing&lt;/h2&gt;
&lt;p&gt;Once your application is running in production it is essential to write logs or use tools like OpenTelemetry to gain 
insights into your running app. When an error occurs and your LLM needs to analyze a bug report, it is very helpful
to provide additional context for the fix. A stacktrace or a long-running trace on a database operation is worth more than
a thousand words and can help the LLM to fix, test and deploy a solution in a short timeframe.&lt;/p&gt;

&lt;p&gt;We all know that new technologies need new approaches in software development, so I hope this article can help you introduce
new strategies and behaviors into your workplace to make the best use of agentic programming. I heard, they might help
human developers, too.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="programming" /><category term="llm" /><summary type="html">Agentic programming (“vibe coding”) is everywhere. In this article I’d like to share my top 6 strategies to support LLM agents in writing, building and deploying good, structured and reliable code:</summary></entry><entry><title type="html">The LISP curse, now coming to your ecosystem!</title><link href="https://javahippie.net/programming/llm/2026/03/05/the-lisp-curse-coming-to-your-ecosystem.html" rel="alternate" type="text/html" title="The LISP curse, now coming to your ecosystem!" /><published>2026-03-05T20:00:00+00:00</published><updated>2026-03-05T20:00:00+00:00</updated><id>https://javahippie.net/programming/llm/2026/03/05/the-lisp-curse-coming-to-your-ecosystem</id><content type="html" xml:base="https://javahippie.net/programming/llm/2026/03/05/the-lisp-curse-coming-to-your-ecosystem.html">&lt;p&gt;The LISP curse is often quoted as the reason why so few libraries and reusable components exist for LISP dialects. According to the concept, LISP is so powerful and flexible that it allows developers to create custom solutions easily, leading to them working longer in isolation. This causes them to often reinvent the wheel, because creating code to solve your own problems can be easier / faster than relying on the libraries (and way of thinking) of other people. This leads to a lack of central frameworks and libraries, like many other language ecosystems have. Sometimes it’s also mentioned that this powerful, flexible code which was written in isolation often lacks documentation and can be hard to comprehend by third parties, so other developers prefer to write their own modules – and the curse keeps on.&lt;/p&gt;

&lt;h2 id=&quot;yes-this-post-is-about-llm-assisted-coding&quot;&gt;Yes, this post is about LLM-assisted coding&lt;/h2&gt;
&lt;p&gt;I was drawn into several discussions this week where people claimed that with the ease and quality of Large Language Models, it is now easier and cheaper to generate code than to use existing abstractions like frameworks or libraries. If we ignore the fact that established libraries are incredibly stable, tested by hundreds of thousands of developers, included in thousands of CI pipelines and maintained by experts on the topic, if we accept the assumption that it is easier to let an LLM generate functionality than to learn the interfaces of existing libraries and frameworks (or let the LLM integrate these libraries or frameworks), what would the implications for language ecosystems like Java’s look like?&lt;/p&gt;

&lt;h2 id=&quot;more-isolation-more-individualism-less-collaboration&quot;&gt;More isolation, more individualism, less collaboration&lt;/h2&gt;
&lt;p&gt;A lot of people contributed to the success of Open Source ecosystems in the last decades through collaboration, collective problem solving and with the goal of solving hard problems.&lt;/p&gt;

&lt;p&gt;The first effect of the aforementioned scenario would be the decline of people using the open source libraries, reducing feedback to the maintainers. If I could just solve my problems with one or two prompts, 20 minutes of waiting time and a couple of dollars worth of tokens, why should I bother with research? Why should I bother with the API decisions other people made? Why should I bother with reading documentation?&lt;/p&gt;

&lt;p&gt;As a second effect, the creation of new Open Source libraries would stagnate. The hard part of doing Open Source is maintenance. Structuring your code, hosting infrastructure, publishing to artifact repositories. Versioning, backwards compatibility, keeping the stack up to date. If generating code is that easy, if I can solve my own problems that quickly, why should I bother modularizing the code, sharing it with the world and doing all the project management an Open Source project needs? I have everything I need in my generated module, I can break the API if I need to, adjust everything exactly to my use case, my stack.&lt;/p&gt;

&lt;p&gt;These two effects might even amplify each other in the long term. If fewer Open Source libraries are provided to integrate new technologies, new approaches into a language ecosystem, people might be even more inclined to just write / generate their own solution and be done with it.&lt;/p&gt;

&lt;h2 id=&quot;a-grim-outlook&quot;&gt;A grim outlook&lt;/h2&gt;
&lt;p&gt;As you might have already read between the lines, I don’t share the assumption that we can skip adding libraries and frameworks to our code and just generate everything instead. I’d rather rely on proven libraries with well-defined abstractions than generate hundreds of lines of code which solve a local problem for me. “Not generated here” is just a different version of “Not invented here”. Still, I think that Large Language Models in coding are here to stay, and that this scenario will arrive in some way. Maybe not as drastically as some thought leaders now proclaim, but by accident. Open Source collaboration will decline to some degree and software development might become a much lonelier task than it is now.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="programming" /><category term="llm" /><summary type="html">The LISP curse is often quoted as the reason why so few libraries and reusable components exist for LISP dialects. According to the concept, LISP is so powerful and flexible that it allows developers to create custom solutions easily, leading to them working longer in isolation. This causes them to often reinvent the wheel, because creating code to solve your own problems can be easier / faster than relying on the libraries (and way of thinking) of other people. This leads to a lack of central frameworks and libraries, like many other language ecosystems have. Sometimes it’s also mentioned that this powerful, flexible code which was written in isolation often lacks documentation and can be hard to comprehend by third parties, so other developers prefer to write their own modules – and the curse keeps on.</summary></entry><entry><title type="html">Self Hosting Reverse Geocoding</title><link href="https://javahippie.net/database/postgis/2026/01/16/selfhosting-reverse-geocoding.html" rel="alternate" type="text/html" title="Self Hosting Reverse Geocoding" /><published>2026-01-16T02:00:00+00:00</published><updated>2026-01-16T02:00:00+00:00</updated><id>https://javahippie.net/database/postgis/2026/01/16/selfhosting-reverse-geocoding</id><content type="html" xml:base="https://javahippie.net/database/postgis/2026/01/16/selfhosting-reverse-geocoding.html">&lt;p&gt;Disclaimer: The dataset used in this blog post is only licensed for non-commercial use. If you need to do reverse geocoding
for a commercial use case, this solution will not work for you.&lt;/p&gt;

&lt;p&gt;I’m currently working on a &lt;a href=&quot;https://feditrack.javahippie.net/timeline&quot;&gt;small hobby project&lt;/a&gt; for visualizing data from fitness trackers. As this should work globally, I am
using the public OSM servers. Hosting my own tileserver would be excessive overengineering, it required 1TB of disk space 
and minimum 64GB of RAM. My request rates are compatible with the terms of conditions of the public servers, anyway.&lt;/p&gt;

&lt;p&gt;While rendering works well, I wanted to add the name and country of the starting location to the uploaded activities to
enable users to search for them. If they wanted to revisit a hike they did while on vacation in Italy, they could enter
“Milano” or simply “Italy” into the search field and filter accordingly.&lt;/p&gt;

&lt;h2 id=&quot;option-1-use-an-api&quot;&gt;Option 1: Use an API&lt;/h2&gt;
&lt;p&gt;There are many public API for reverse geocoding, e.g. the &lt;a href=&quot;https://nominatim.org/release-docs/latest/api/Overview/&quot;&gt;Nominatim API&lt;/a&gt;. Simply using
this API would have been the easiest way, but the software supports batch imports of activities, I imported roughly 850 activities at once
from my Strava export. Nominatims &lt;a href=&quot;https://operations.osmfoundation.org/policies/nominatim/&quot;&gt;usage policy&lt;/a&gt; asks users to limit themselves
to 1 request per second at most. This would leave me with paid alternatives, or an internal queue with a rate limiter which processes
these lookups asynchronously. I decided against this, because I don’t want too many external dependencies in my application,
and an asynchronous, rate limited lookup would have increased the complexity of the architecture.&lt;/p&gt;

&lt;h2 id=&quot;option-2-using-the-geonames-dataset&quot;&gt;Option 2: Using the Geonames dataset&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.geonames.org/&quot;&gt;Geonames&lt;/a&gt; is a popular dataset which contains geographical data. Unfortunately for every city in the 
dataset there is only one location stored in the data, which resembles the center of the city. In urban areas with many neighbouring
cities, this would certainly lead to overlaps. I am living in Mainz, and if I started my workout on the banks of the Rhine, the closest 
city center might be Wiesbaden, which is located on the opposite side of the river. For a single activity upload this would have been
okayish, because I could have provided two or three suggestions to the user. But this would have caused more work for the users and
would not have worked for batch updates.&lt;/p&gt;

&lt;h2 id=&quot;option-3-using-the-gadm-dataset&quot;&gt;Option 3: Using the GADM dataset&lt;/h2&gt;
&lt;p&gt;After some further research I found the &lt;a href=&quot;https://gadm.org/data.html&quot;&gt;GADM data&lt;/a&gt;. The aim of the project is to map the administrative 
data of all countries at all levels. &lt;a href=&quot;https://gadm.org/license.html&quot;&gt;The license&lt;/a&gt; grants usage for scientific and other non-commercial
use cases, which fits my project. Commercial licensing of the data is not possible. In contrast to the Geonames dataset, the GADM data
provides the shapes of all countries, states, municipalities, cities, so with PostGIS (which the software already uses) it is possible
to check if coordinates of a location are in the exact boundaries of such an administrative area. If I started my workout on the banks
of the Rhine now, it would really return “Mainz”, even to the middle of the river. This is the option I implemented&lt;/p&gt;

&lt;h2 id=&quot;gathering-and-importing-the-data-into-postgis&quot;&gt;Gathering and importing the data into PostGIS&lt;/h2&gt;
&lt;p&gt;I never worked with geographical data before, but I was pleasantly surprised about the quality of the tooling. The first 
step was to download the whole GADM data as a Zip file from https://gadm.org/download_world.html. The Zip file has a size 1.4GB,
it contains a database in the GeoPackage format which has ~2.8GB uncompressed.&lt;/p&gt;

&lt;p&gt;To import the data into a running PostGIS database I used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ogr2ogr&lt;/code&gt; Tool provided by &lt;a href=&quot;https://gdal.org/en/stable/programs/ogr2ogr.html&quot;&gt;GDAL&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ogr2ogr &lt;span class=&quot;nt&quot;&gt;-nln&lt;/span&gt; public.gadm_410 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-lco&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;GEOMETRY_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;geom &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-lco&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;FID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fid &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; PostgreSQL PG:&lt;span class=&quot;s2&quot;&gt;&quot;host=localhost port=5432 dbname=testdb user=test password=test&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-progress&lt;/span&gt;  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        gadm_410.gpkg      
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The tool ships with a PostgreSQL driver which creates a table and imports the data into a Postgres database with the PostGis extension. 
We specify that the table should be called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gadm_410&lt;/code&gt; in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public&lt;/code&gt; schema. The column containing the geo data is named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geom&lt;/code&gt;, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fid&lt;/code&gt; from
the GeoPackage database should be mapped to a column named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fid&lt;/code&gt;. Then we specify the connection details for our Postgres instance, and add a 
flag to make the tool print the progress while it is importing. The last parameter is the GeoPackage database itself.&lt;/p&gt;

&lt;p&gt;Depending on the hardware the import takes some time. While running the import on my server for the production app my disk space ran out,
so be aware that this process takes quite some disk space while the tool unpacks and imports the data.&lt;/p&gt;

&lt;h2 id=&quot;doing-reverse-lookups-in-sql&quot;&gt;Doing reverse lookups in SQL&lt;/h2&gt;
&lt;p&gt;The database is now populated with all the data, the DDL for the table created by ogr2ogr looks like this:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gadm_410&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fid&lt;/span&gt;        &lt;span class=&quot;nb&quot;&gt;serial&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;uid&lt;/span&gt;        &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gid_0&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_0&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;varname_0&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;29&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gid_1&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_1&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;varname_1&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;82&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nl_name_1&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;87&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;iso_1&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hasc_1&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_1&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;type_1&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;engtype_1&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;validfr_1&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gid_2&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_2&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;varname_2&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nl_name_2&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hasc_2&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_2&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;type_2&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;engtype_2&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;validfr_2&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gid_3&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_3&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;38&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;varname_3&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;44&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;nl_name_3&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;56&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;hasc_3&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;27&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_3&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;type_3&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;engtype_3&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;validfr_3&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gid_4&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_4&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;varname_4&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_4&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;type_4&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;29&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;engtype_4&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;29&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;validfr_4&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;gid_5&lt;/span&gt;      &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name_5&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;34&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cc_5&lt;/span&gt;       &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;type_5&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;engtype_5&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;governedby&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sovereign&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;disputedby&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;region&lt;/span&gt;     &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;varregion&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;country&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;continent&lt;/span&gt;  &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;subcont&lt;/span&gt;    &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;geom&lt;/span&gt;       &lt;span class=&quot;n&quot;&gt;geometry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MultiPolygon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geodata_pkey_geom_geom_idx&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gadm_410&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gist&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;geom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The columns with a numbered postfix map to the different levels. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name_0&lt;/code&gt; is the highest area, the country, e.g. “Germany”. 
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name_1&lt;/code&gt; is a state for most countries, so for Mainz “Rhineland Palatinate” or “Rheinland Pfalz”, and so on. The city name 
is located in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name_4&lt;/code&gt; for most entries, but there are also entries without a city name. A workout I did during my vacation was 
started on the beach of the island “Texel” in the Netherlands, and there is no city connected to it, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name_4&lt;/code&gt; is empty.&lt;/p&gt;

&lt;p&gt;For my application I want a formatted string, e.g. “Mainz, Germany” or “Texel, Netherlands”. To do this, we select all existing
names via SQL which match the coordinates we are looking for. This example shows a place on the Rhine in Mainz:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_5&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gadm_410&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ST_Intersects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ST_SetSRID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ST_Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;269759&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;009428&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;with the result&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;fid&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_0&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_1&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_2&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_3&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_4&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_5&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;100041&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Germany&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Rheinland-Pfalz&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Mainz&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Mainz&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Mainz&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The cities name “Mainz” appears three times, because as a “Kreisfreie Stadt” it is its own district and its own
“Verbandsgemeinde” (municipality).&lt;/p&gt;

&lt;p&gt;This example shows a place on Texel:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name_5&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gadm_410&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ST_Intersects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ST_SetSRID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ST_Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;869232&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;53&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;155858&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4326&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;geom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;with the result&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;fid&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_0&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_1&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_2&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_3&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_4&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;name_5&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;215668&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Netherlands&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Noord-Holland&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Texel&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Due to the index on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geom&lt;/code&gt; column, a lookup takes 393 ms on my 2020 M1 Macbook Pro on a cold database.&lt;/p&gt;

&lt;h2 id=&quot;i-didnt-read-this-i-want-a-complete-script-to-run-this-and-try-it-out&quot;&gt;I didn’t read this, I want a complete script to run this and try it out!&lt;/h2&gt;
&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; my-postgis &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;testdb &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 5432:5432 &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; postgis/postgis
wget https://geodata.ucdavis.edu/gadm/gadm4.1/gadm_410-gpkg.zip
unzip gadm_410-gpkg.zip
ogr2ogr &lt;span class=&quot;nt&quot;&gt;-nln&lt;/span&gt; public.gadm_410 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-lco&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;GEOMETRY_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;geom &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-lco&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;FID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;fid &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-skipfailures&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; PostgreSQL PG:&lt;span class=&quot;s2&quot;&gt;&quot;host=localhost port=5432 dbname=testdb user=test password=test&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-progress&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        gadm_410.gpkg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Tim Zöller</name></author><category term="database" /><category term="postgis" /><summary type="html">Disclaimer: The dataset used in this blog post is only licensed for non-commercial use. If you need to do reverse geocoding for a commercial use case, this solution will not work for you.</summary></entry><entry><title type="html">Agile frameworks are like morning routines</title><link href="https://javahippie.net/agile/scrum/2025/02/03/agile-frameworks-are-like-morning-routines.html" rel="alternate" type="text/html" title="Agile frameworks are like morning routines" /><published>2025-02-03T08:00:00+00:00</published><updated>2025-02-03T08:00:00+00:00</updated><id>https://javahippie.net/agile/scrum/2025/02/03/agile-frameworks-are-like-morning-routines</id><content type="html" xml:base="https://javahippie.net/agile/scrum/2025/02/03/agile-frameworks-are-like-morning-routines.html">&lt;p&gt;Last week I saw a post on LinkedIn describing Hal Elrod’s ‘Miracle Morning’ routine. The poster had set it up as follows&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Get up at 5am.&lt;/li&gt;
  &lt;li&gt;Drink a large glass of water&lt;/li&gt;
  &lt;li&gt;Exercise, meditation, affirmations and visualisation&lt;/li&gt;
  &lt;li&gt;Journaling for yesterday and today&lt;/li&gt;
  &lt;li&gt;Determine the most important tasks for the day&lt;/li&gt;
  &lt;li&gt;Read and consume inspirational or educational content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The routine is based on the book ‘Miracle Morning’ and has many fans around the world. I don’t want to deny that it works for many people, but I still had to laugh at the post because such a morning routine is completely out of reach for me as a parent (which I also addressed &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7290991643200016385/&quot;&gt;in a post&lt;/a&gt;). This is a common aspect of morning routines that are advertised and sold: They worked well for the author, and the author now wants to help other people with them, show their own problem solving and make their lives better - well, that and make a lot of money.&lt;/p&gt;

&lt;p&gt;The next day at work I realised that I have a similar problem with agile frameworks: they worked for someone at some point (except for the Spotify model, &lt;a href=&quot;https://www.jeremiahlee.com/posts/failed-squad-goals/&quot;&gt;which Spotify never used&lt;/a&gt;), and now they want to make the world a better place and help organisations adopt the agile approach that worked for them - well, that, and make a lot of money.&lt;/p&gt;

&lt;p&gt;Neither morning routines nor agile frameworks are bad ideas. But the idea that they are suitable for everyone and need to be implemented 1:1 is abstruse. I certainly can’t follow the Miracle Morning Routine, but I could easily incorporate a little journaling or exercise into my morning routine and establish a regularity that benefits me. A well-functioning agile development team does not need the tight guardrails of the Scrum process, but it can benefit from the continuity of some appointments, while an inexperienced team can easily enter agile development through the entire Scrum process.&lt;/p&gt;

&lt;p&gt;The big problem that both morning routines and agile frameworks have is the part about ‘making money’: When people hire a (bad) coach at a high price, or a (bad) service provider who designs an agile approach for them, it takes a lot of confidence and experience to break out of the rigid frameworks when you realise that your situation doesn’t quite fit them. People and organisations with these qualities will not usually be the ones to hire such coaches and service providers. On the contrary, dubious service providers often shirk their responsibilities: ‘My approach didn’t work BECAUSE you deviated from it. It’s not my fault that you left the prescribed path.’&lt;/p&gt;

&lt;p&gt;In the end, standardised procedures are a good basis for discussion and a source of inspiration, but they do not exempt us from self-reflection and personal responsibility. Not even if the creators of these frameworks have benefited massively from them - well, and made a lot of money.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="agile" /><category term="scrum" /><summary type="html">Last week I saw a post on LinkedIn describing Hal Elrod’s ‘Miracle Morning’ routine. The poster had set it up as follows Get up at 5am. Drink a large glass of water Exercise, meditation, affirmations and visualisation Journaling for yesterday and today Determine the most important tasks for the day Read and consume inspirational or educational content</summary></entry><entry><title type="html">Tuning the Operaton (or Camunda) Job Executor with SQL</title><link href="https://javahippie.net/java/bpmn/operaton/2024/12/15/for-update-skip-locked.html" rel="alternate" type="text/html" title="Tuning the Operaton (or Camunda) Job Executor with SQL" /><published>2024-12-15T13:00:00+00:00</published><updated>2024-12-15T13:00:00+00:00</updated><id>https://javahippie.net/java/bpmn/operaton/2024/12/15/for-update-skip-locked</id><content type="html" xml:base="https://javahippie.net/java/bpmn/operaton/2024/12/15/for-update-skip-locked.html">&lt;p&gt;I am part of a group of people who decided to fork Camunda 7, an open source BPMN engine for Java and other languages, in October. This was in response to Camunda’s announcement of the engine’s end of life in 2025. We are convinced that the technology is still useful for many people and that Camunda has built a robust engine, so we announced &lt;a href=&quot;https://github.com/operaton/operaton&quot;&gt;Operaton&lt;/a&gt; as a community-driven fork to keep it alive in the future.&lt;/p&gt;

&lt;h2 id=&quot;the-job-executor&quot;&gt;The Job Executor&lt;/h2&gt;

&lt;p&gt;One of the most criticized aspects of the engine is the way the job executor works. Operaton will put all jobs which have to be picked up by the process engine into a single table, ACT_RU_JOB with the following structure:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ACT_RU_JOB&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ID_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;REV_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;integer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;TYPE_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LOCK_EXP_TIME_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LOCK_OWNER_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;EXCLUSIVE_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;PRIORITY_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bigint&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LAST_FAILURE_LOG_ID_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;BATCH_ID_&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ID_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you are scaling Operaton horizontally by adding new nodes and pointing them to the same database, all these nodes will query this table for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; latest jobs, retrieve them and lock them. This will not be achieved by row level locks in the database but by setting a logical lock by updating the table &lt;em&gt;after&lt;/em&gt; fetching the rows:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20241215/UML.png&quot; alt=&quot;A UML diagram decscribing the flow metnioned above visually&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lockJob&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;AcquirableJobEntity&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lockOwner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jobExecutor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLockOwner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setLockOwner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lockOwner&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

   &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lockTimeInMillis&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jobExecutor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLockTimeInMillis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

   &lt;span class=&quot;nc&quot;&gt;GregorianCalendar&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gregorianCalendar&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GregorianCalendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;gregorianCalendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ClockUtil&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCurrentTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;gregorianCalendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Calendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MILLISECOND&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lockTimeInMillis&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
   &lt;span class=&quot;n&quot;&gt;job&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setLockExpirationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gregorianCalendar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
 &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This means that two job executors could theoretically retrieve the list – which happens, if there are many concurrent process engines querying for the same table. If this happens, there is an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.operaton.bpm.engine.impl.db.entitymanager.OptimisticLockingListener&lt;/code&gt; registered, which will remove the acquired job from the list of results if such a collision happens. In high load scenarios it is possible that all returned rows from a job query are already being processed, meaning no work will be done in one cycle.&lt;/p&gt;

&lt;h2 id=&quot;trying-to-optimize-for-a-higher-workload&quot;&gt;Trying to optimize for a higher workload&lt;/h2&gt;

&lt;p&gt;The strategy for querying these jobs can be adjusted with parameters, and some weeks ago &lt;a href=&quot;https://forum.operaton.org/t/job-execution-rejected/68&quot;&gt;an interesting question was asked&lt;/a&gt; in our Operaton forum by Jean Robert Alves, a software architect working with Camunda 7. Anticipating a high  workload they will have to deal with in the nearer future he was trying to tune the job executor to his needs. He had a great test setup and was able to tune and compare different configurations in terms of execution time. The results were less than ideal, confirming the preconception I mentioned earlier about the performance of the job executor at scale.&lt;/p&gt;

&lt;p&gt;Now that we have forked the engine and can set the roadmap and implement new features, I was curious if there was a way to improve this locking situation with plain SQL. The engine supports multiple RDMBSs, but the queries are implemented as plain SQL with MyBatis and it would be possible to modify and optimize them, even with different tweaks for different database vendors, if necessary.&lt;/p&gt;

&lt;p&gt;Since the instance discussed in this thread was running on PostgreSQL I was &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-select.html&quot;&gt;reading documentation&lt;/a&gt; and discovered the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SKIP LOCKED&lt;/code&gt; clause in the paragraph about locking mechanisms:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;With SKIP LOCKED, any selected rows that cannot be immediately locked are skipped. Skipping locked rows provides an inconsistent view of the data, so this is not suitable for general purpose work, but can be used to avoid lock contention with multiple consumers accessing a queue-like table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounded like exactly what we needed: Every query to the job executor table now omits rows which are already locked from the result set, and if an Operaton instance wants to query 10 jobs for execution, they will retrieve the next 10 jobs which are already locked. In a quickly hacked Camunda instance, Jean Robert was &lt;a href=&quot;https://forum.operaton.org/t/job-execution-rejected/68/50?u=javahippie&quot;&gt;able to confirm performance improvments from 40 minutes down to 16 minutes&lt;/a&gt;. The same syntax also works for MariaDB, MySQL and Oracle DB.&lt;/p&gt;

&lt;h2 id=&quot;implementing-the-feature-in-operaton&quot;&gt;Implementing the feature in Operaton&lt;/h2&gt;
&lt;p&gt;I started implementing this feature in Operaton, you can already try it out by building Operaton from &lt;a href=&quot;https://github.com/operaton/operaton/tree/feature/add-configuration-for-skipping-locked-rows&quot;&gt;this branch&lt;/a&gt;. Properties can be passed through by Spring Boot properties, other environments were not implemented or tested yet.&lt;/p&gt;

&lt;p&gt;The two questions we need to answer before including this as an (opt in) feature for the engine for everybody are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Some supported databases don’t support this syntax, IBM DB2 and MSSQL. Is there another way to achieve this, or will users of those databases be left out? DB2s “SKIP LOCKED DATA” clause and MS SQLs “UPDLOCK / READPAST” feature look similar, but need to be evaluated.&lt;/li&gt;
  &lt;li&gt;Testing, testing, testing. While we plan to create a feature flag to enable this behavior, we need to make sure it provides the quality a process engine needs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m looking forward to receive feedback from people using this feature in real-world settings and I’d be excited if this feature made the engine perform better for certain usecases.&lt;/p&gt;

&lt;h2 id=&quot;outlook&quot;&gt;Outlook&lt;/h2&gt;
&lt;p&gt;If you are interested in this topic you are welcome to subscribe to our forum or &lt;a href=&quot;https://github.com/operaton/operaton/issues/264&quot;&gt;watch the issue on GitHub&lt;/a&gt;.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="java" /><category term="bpmn" /><category term="operaton" /><summary type="html">I am part of a group of people who decided to fork Camunda 7, an open source BPMN engine for Java and other languages, in October. This was in response to Camunda’s announcement of the engine’s end of life in 2025. We are convinced that the technology is still useful for many people and that Camunda has built a robust engine, so we announced Operaton as a community-driven fork to keep it alive in the future.</summary></entry><entry><title type="html">Unit testing with LocalDateTime.now() in Java</title><link href="https://javahippie.net/java/spring/2024/08/15/testing-localdatetime-now-in-java.html" rel="alternate" type="text/html" title="Unit testing with LocalDateTime.now() in Java" /><published>2024-08-15T06:45:00+00:00</published><updated>2024-08-15T06:45:00+00:00</updated><id>https://javahippie.net/java/spring/2024/08/15/testing-localdatetime-now-in-java</id><content type="html" xml:base="https://javahippie.net/java/spring/2024/08/15/testing-localdatetime-now-in-java.html">&lt;p&gt;When writing unit tests, we expect the classes under test to be in a certain state that we can set up in our test. A dynamic state, which is initialised in the class itself, makes our unit testing life much harder. In this short post I will share my approach to make classes using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDateTime.now()&lt;/code&gt; more testable in a Spring Boot application.&lt;/p&gt;

&lt;h2 id=&quot;the-issue&quot;&gt;The issue&lt;/h2&gt;
&lt;p&gt;We want to write a unit test for the following class:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As the method itself looks up the current date and time, we need to manipulate the post date to test the application logic:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorServiceTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testPostCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;minusDays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testInvoiceDateIsInvalid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;plusDays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isFalse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We ignore the fact that this is not a good way to write a validator, and that the method could be placed on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post&lt;/code&gt; class itself instead of writing a validator to the class instead. Instead, we will look at the test setup and notice that it is not very declarative. We initialise our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Post&lt;/code&gt; instance with a date time that is exactly one day in the past or one day in the future, depending on what we want to test.&lt;/p&gt;

&lt;p&gt;This shows our intent, but prevents us from testing edge cases. What if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;minusDays(1)&lt;/code&gt; puts our date time in the previous month? The previous year? What if it is a leap year? What if the clocks have changed to daylight saving time? We cannot set up a test to answer these questions. If the tests are run at times that involve these edge cases, they may randomly fail.&lt;/p&gt;

&lt;h2 id=&quot;the-naive-solution&quot;&gt;The naive solution&lt;/h2&gt;
&lt;p&gt;We could decide to add the value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;now()&lt;/code&gt; to the method signature:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;    
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case we can decide what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;now&lt;/code&gt; is in our testcase, and create a better unit test:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorServiceTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testPostCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;postDate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;postDate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We are now the masters of time when it comes to unit testing. However, we have made the API of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PostValidatorService&lt;/code&gt; much worse. The signature is cluttered
with the current date and time throughout our application. Also, if we wrote additional unit tests, we realised that we had just moved the problem. Someone &lt;em&gt;has&lt;/em&gt; to decide what &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;now&lt;/code&gt; is at some point in our application, and we would want to unit test that part of the application as well. It would be helpful to provide a component that our instance can ask for the current time without calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDateTime.now()&lt;/code&gt; itself. Fortunately, such a component exists in Java.&lt;/p&gt;

&lt;h1 id=&quot;using-javatimeclock&quot;&gt;Using java.time.Clock&lt;/h1&gt;
&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; class was &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html&quot;&gt;introduced in Java 8&lt;/a&gt; as part of the date and time API. It can be configured centrally in our application, passed to our classes in the constructor and used by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDateTime.now()&lt;/code&gt; and similar methods to find the current time &lt;em&gt;given the configured clock&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In a unit test we can set up the instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; with the method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fixed&lt;/code&gt; and pass it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;testee&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PostValidatorServiceTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testPostCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2024-08-15T08:15:00.00Z&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt; 
                                  &lt;span class=&quot;nc&quot;&gt;ZoneId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Europe/Berlin&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        
    
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;postDate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2024&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PostValidatorService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setPostCreationTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;postDate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;testee&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;postCreationTimeIsValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;n&quot;&gt;assertThat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we can even control the time zone our clock is in, and cover a whole new class of time-based errors. The signature of our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;postCreationTimeIsValid&lt;/code&gt; method is cleaned up again, and we can implement a variety of test cases without relying on the current date and time (and without using a mocking library).&lt;/p&gt;

&lt;h2 id=&quot;injecting-the-clock-into-a-spring-boot-application&quot;&gt;Injecting the Clock into a Spring Boot application&lt;/h2&gt;
&lt;p&gt;In modern Java frameworks, we do not instantiate our classes by hand, we use inversion of control and let the framework inject the dependencies. In a Spring Boot application, our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PostValidatorService&lt;/code&gt; would probably be annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Service&lt;/code&gt;. If we add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; parameter to the constructor, our framework would look up an instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; to inject into our service - and fail. We need to provide one for our application context:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ClockConfiguration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Bean&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Could also use `systemUTC()` &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// or `system(ZoneId zoneId)` to &lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// configure the time zone of our application.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;systemDefaultZone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now every part of our application can inject the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; instance and use it to look up the current date and time. When writing integration tests, we could also use this to start our entire application context at a particular time to better define edge cases in our integration tests.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; in our applications will help us write better tests. In modern frameworks using dependency injection, maintaining a central instance of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; requires very little overhead and won’t clutter up our method signatures. While our code will still have side effects, we can now control these side effects in the setup of our unit tests and avoid broken tests.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="java" /><category term="spring" /><summary type="html">When writing unit tests, we expect the classes under test to be in a certain state that we can set up in our test. A dynamic state, which is initialised in the class itself, makes our unit testing life much harder. In this short post I will share my approach to make classes using LocalDateTime.now() more testable in a Spring Boot application.</summary></entry><entry><title type="html">Large Language models - A Street Full of Wrong-Way Drivers?</title><link href="https://javahippie.net/artificial-intelligence/llm/2024/05/05/llm-wrong-way-drivers.html" rel="alternate" type="text/html" title="Large Language models - A Street Full of Wrong-Way Drivers?" /><published>2024-05-05T18:00:00+00:00</published><updated>2024-05-05T18:00:00+00:00</updated><id>https://javahippie.net/artificial-intelligence/llm/2024/05/05/llm-wrong-way-drivers</id><content type="html" xml:base="https://javahippie.net/artificial-intelligence/llm/2024/05/05/llm-wrong-way-drivers.html">&lt;p&gt;If you follow me on social media, you know I am deeply sceptical about the benefits of AI in most usecases. I am critical of the environmental impact, I am worried about the centralization on a few SaaS providers and I am sad about the topic dominating every product roadmap, using up ressources which could have been used to progress the products exiting features further. On the other hand I talked about this topic with a lot of smart people this year – and many praised large language models and told me how they made their daily work easier. I’m writing this post, to reflect about my opinion and how arrogant it would be to dismiss peoples first hand experience without second guessing myself. If everybody seems to be a wrong-way driver, it is probably a good idea to check if you are yourself not the one going the wrong way.&lt;/p&gt;

&lt;h2 id=&quot;why-i-wont-talk-about-ethics-and-ecological-impact&quot;&gt;Why I won’t talk about ethics and ecological impact&lt;/h2&gt;
&lt;p&gt;While these two aspects of large language models are my biggest issues, this won’t be a part of the upcoming paragraphs. I took (probably useless) measures that my blog posts and code are not ingested by large language models because I don’t want to contribute free labor for multi-billion-dollar software companies. I also think that the current hype about training models will negatively impact the efforts in fighting climate change. In this post however, I would like to ask the question “Are large language models useful”, not “Are they worth it?”, to simplify things. I also will not view this topic from a perspective of “All models will get better in the future”. Sure they will, but we don’t know when and how much. Addresing all shortcomings with “The future will fix it” helps nobody and is a bad way of discussing things.&lt;/p&gt;

&lt;h2 id=&quot;the-conversation-triggering-this-post&quot;&gt;The conversation triggering this post&lt;/h2&gt;
&lt;p&gt;Some weeks ago I participated in the “Digital Crafts Day” as a speaker. The crowd was a mix of the usual developer nerds, students and people working in different industries. As the event was happening at a university, the average participant was rather young, and AI was a very present topic. During a coffee break, I talked to two people working for the public sector, and they told me their AI success story: While modernizing their software stack for some years now, they still have a lot of COBOL code on IBM host sytems. For some months now, they use AI tools to translate this COBOL code into Java, and they were really happy with the results so far. My internal dialogue while listening to their experience was:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Oh god, no&lt;/li&gt;
  &lt;li&gt;This cannot work&lt;/li&gt;
  &lt;li&gt;The code quality must be horrible&lt;/li&gt;
  &lt;li&gt;How can you even translate COBOL code into OOP concepts with Java?&lt;/li&gt;
  &lt;li&gt;What are they gaining from this?&lt;/li&gt;
  &lt;li&gt;They will suffer from this in the future&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some points, my own thoughts felt a little embarrassing to me. There I was, talking to experienced professionals, software developers with at least as much experience as myself, and I rejected their experience based on my own preconceptions - without having shared their experience, or having tried similar things. Of course I did not say any of these things out loud. At two other conferences afterwards I shared their experience with a few other people whos skills I value very much, and asked for their opinion on this. They almost exactly monitored my own thoughts listed above. This pretty much sums up the two opinions I see only quite often: The people advocating for LLMs in writing code or even transforming code, and the people rejecting the very idea. I realized that the “contra” point of view was often backed by emotions and fear that other people would be using those tools poorly - and it was a little painful to admit that this was the case for myself, too.&lt;/p&gt;

&lt;h2 id=&quot;how-i-currently-use-large-language-models&quot;&gt;How I currently use large language models&lt;/h2&gt;
&lt;p&gt;Right now I have two usecases which benefit from large language models: Translating texts and text snippets in a professional setting and writing and explaining bash scripts.&lt;/p&gt;

&lt;p&gt;My mother tongue is German, and while I consider my English to be quite good, I am sure that many expressions and metaphors in my english texts work better in German, and no native speaker would write them this way. When participating in CfPs for international conferences, I use ChatGPT to correct my pre-written english abstracts. My prompt is usually “Please correct the following text, written by a German native speaker in english, correct spelling errors and grammar and make it more idiomatic”. If my abstracts are longer than the maximum character count allowed, I ask ChatGPT to shorten them. In my opinion, ChatGPT can work as an equalizer, giving non-native speakers the possibility to write idiomatic and error-free texts, maybe increasing their chance of getting talks, applications or proposals accepted. This text was not improved by any LLM, I don’t usually do this with my blog posts.&lt;/p&gt;

&lt;p&gt;My other usecase, writing and explaining bash scripts, helps me because I really, really suck at bash. I am able to read and understand most scripts, but I write them so rarely, that I forget most of the syntax. While writing a short script to automate a recurring task on my laptop, I tried asking ChatGPT to create the script for me. I could understand the generated script, validate that it would do the task while doing no harm to my laptop, and I have been doing this ever since. It helps me finishing these chores much faster, as I don’t have any intention to learn bash on a higher level.&lt;/p&gt;

&lt;h2 id=&quot;letting-ai-write-my-code&quot;&gt;Letting AI write my code&lt;/h2&gt;
&lt;p&gt;I experimented with letting AI write my code – both ChatGPT and IntelliJ’s built in assistant. To understand my main focus, it is important to know that I work as an IT-Consultant for software development and -architecture. I usually work on Enterprise codebases which pull data from several sources, display it to users, lets users interact with it and write changes or work on processes with it. I rarely write complicated algorithms, my main task is making decisions about data:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Which system provides my data?&lt;/li&gt;
  &lt;li&gt;Which interfaces does this system provide?&lt;/li&gt;
  &lt;li&gt;How does the contract to this interface look like?&lt;/li&gt;
  &lt;li&gt;How can I transform and merge the data?&lt;/li&gt;
  &lt;li&gt;Can I find common abstractions for specific datasets which are robust?&lt;/li&gt;
  &lt;li&gt;Where are the transaction boundaries (if there are any)?&lt;/li&gt;
  &lt;li&gt;How can I encapsulate the business logic transforming the data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the time I spend thinking at work, I don’t think about the code I am writing, I am thinking about the concepts behind it. When writing code, I usually don’t type that much. REST-Controllers, Kafka Integrations and others don’t need much boilerplate code, as modern frameworks like Spring Boot already provide excellent abstractions over those technologies. Every time I tried generating code like this, my observation was that I spent as much time checking the code for edge cases and correctness as I would have spent writing the code in the first place. To be fair, the generated code was almost always correct, but only &lt;em&gt;almost&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The same is true when I tried generating Unit Tests. The tests were usually really good, but when I am writing Unit Tests, testing my code for correctness is only one of my goals. The other one is to use the tests as a tool to think about my code for a second time, from a different angle. It does not only help me think about the edge cases (which the AI-generated code does, too), but quite often, while thinking about &lt;em&gt;what to test&lt;/em&gt;, I realize that my initial assumptions while writing the functionality were flawed.&lt;/p&gt;

&lt;p&gt;Right now I have 15 years of experience writing code, mostly in Java with JavaEE, Spring and Spring Boot. By now I know the language and the frameworks really well, and I hardly have to look up documentation or to use Google to help me find the correct features and classes. Considering that my code mostly orchestrates data and makes it available to other systems, I have concluded that large language models don’t help me becoming more productive at my main job. When talking to other professional software developers who work in a simliar setting, most of them agreed with this sentiment.&lt;/p&gt;

&lt;h2 id=&quot;usecase-1-assisting-junior-developers&quot;&gt;Usecase 1: Assisting Junior Developers&lt;/h2&gt;
&lt;p&gt;At one point in the past, a Junior Developer created a pull request in our project dealing with monetary values. They used a floating point datatype to store the value, while doing calculations on it. I commented on the PR, why a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt; datatype would be more fitting, and how to deal with rounding and which types of rounding they could use in which case and what was important about precision. Our junior developer learned something about Java and datatypes. Some time later I was curious and asked ChatGPT to generate the code for me, only stating the requirements. The generated code was way better than the first draft of our junior, but it set the precision of the datatype incorrectly, leading to rounding errors for some rare cases. A developer who did not know about the peculiarities of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt; would probably have missed this detail, in the best case the rounding errors would have been caught in unit tests. This was not a general flaw of ChatGPT, but the decision when to apply the precision to an intermediate result very much depends on the type of calculation you do. One could argue that the requirement was not precise enough, but I believe it was sufficient for most developers.&lt;/p&gt;

&lt;p&gt;Another aspect of this anecdote is the “learning” part. After making the mistake, our junior developer had a story in their head which they could connect to the features of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt;. That does not necessarily mean they won’t make a similar mistake again, but I’d argue that they learned about this error category to some extent. To compare myself with more junior developers, I tried writing a Python web application with the help of ChatGPT. I do not know Python well and only have used it sporadically.&lt;/p&gt;

&lt;p&gt;After two or three evenings I had a Python Web app with Flask running and it did what I wanted from it. My experience was mixed, as the recommended Python functions could always be overloaded, and my applications context was not always that well known to ChatGPT. For many cases I ended up looking up the documentation for the libraries or APIs, and finding a better fit. Nevertheless, I would not have been able to write this application this fast without ChatGPT. My main issue is, that I would not be able to replicate this without any support of an LLM. I could look up my existing code and copy a lot from it, but I have not learned as much as I did learn from similar hobby-projects in languages I didn’t know too well (I am also sure, that experienced Python developers would shake their heads while reading my code).&lt;/p&gt;

&lt;p&gt;To summarize this usecase: While I do believe, that junior developers and even mid-level developers could finish programming tasks faster with the support of an LLM, I am not too sure about the educational aspects. If we still wanted those junior developers to progress to senior developers with similar experience and a similar skillset as we have today, I’d argue that we need a new approach to education to counterbalance the effects of LLMs. This assumption is supported by an anecdote a speaker at JAX conference told me this year: They ordered a junior developer to extend an existing program, and they did the job well. In an in-person code review they asked about the code and his decision process and pointed out, two or three things that were not quite right, yet. The junior developer was not able to adapt the code, or even explain everything it did. They confessed using AI support to write it. While the result was mostly very good, there was no learning experience, and maintaining the code would have become harder and harder for the junior developer.&lt;/p&gt;

&lt;h2 id=&quot;usecase-2-migrating-large-codebases-to-a-new-technology&quot;&gt;Usecase 2: Migrating large Codebases to a new technology&lt;/h2&gt;
&lt;p&gt;Earlier I mentioned that the biggest part of my job is thinking about data and the concepts behind it. But what if the data model, the business logic and even the abstractions behind it are already established? This matches the conversation about migrating a COBOL codebase, which I mentioned in the introduction. They migrated code which was already in place for decades to a new stack, so they can get rid of the expensive IBM host systems in the near future. Unfortunately, I don’t know about the tools they used for their specific project, but I can imagine this working quite well - depending on the goal of the migration.&lt;/p&gt;

&lt;p&gt;I don’t think that their goal was to embed the COBOL funtionality into an existing Java code base, make it use object oriented patterns and leverage the functionality of a framework like Spring Boot. Existing abstractions in the data would have to be redesigned and rearranged to match the different paradigms of an object oriented language. If I planned such a task, I would make sure that the migrated COBOL code was encapsulated in its own application, with existing code accessing it via well defined interfaces. The first impression might be, that we now have moved the code from one silo which is not easy to maintain into another silo which is not easy to maintain, and this might be true – but this new silo can now be operated at a fraction of the cost of the old one. There is no need to purchase an expensive new IBM host system in the future, and no need to keep paying IBM for licenses and transactions.&lt;/p&gt;

&lt;p&gt;This would still be the second best approach. The best approach would be to migrate the old code into a new, idiomatic Java codebase which makes use of all provided features of the language and the framework. Allocating budget for this task, however, is not an easy thing to do in a big corporation. The developers are expected to provide new features to users, and not spend a year working on something which then works exactly as it did before. This way of thinking is short sighted, but unfortunately present in too many companies (at least in Germany). Reducing the migration- and testing-effort with AI tooling could be a compromise.&lt;/p&gt;

&lt;h2 id=&quot;usecase-3-assisting-in-languages-which-are-used-rarely&quot;&gt;Usecase 3: Assisting in languages which are used rarely.&lt;/h2&gt;
&lt;p&gt;I mentioned earlier in this text, that I already use LLMs to write bash scripts. When talking to other developers, even AI-sceptical ones, this usecase is brought up a lot: People use ChatGPT to write SQL queries, SPARQL queries or regular expressions – languages which they do not use that often, which makes them look up documentation every time again. This fits a sentiment on LLM-generated texts I heard quite often: They can be used in cases where errors don’t matter that much, or where the result can be checked. For me, this seems to be the most interesting usecase so far. It saves a lot of time, the effect on learning language features is neglectable, as these features and languages are only rarely used by the user of the LLM, and the intervals are often too long for a learning effect to set in. Unfortunately, this niche is not the one which makes for great marketing. GitHub promises huge productivity gains with CoPilot. The marketing claim “Really helps you write this annoying code in that language you don’t know that well, but only need every two months” does not have such a nice ring to it.&lt;/p&gt;

&lt;h2 id=&quot;and-what-now&quot;&gt;And what now?&lt;/h2&gt;
&lt;p&gt;The anecdote about the COBOL migration made me realize that I was judging people using LLMs for more complex tasks from a point of emotion. As written above, after thinking about it for some time I could see this working for some use cases, depending on the expectations. While I still see issues with using LLMs for our everyday programming tasks, it might be beneficial to listen to the real people who were successful in applying it. Dismissing these stories and the experience of these people would be arrogant. They are not the people selling AI products to you and have no benefit of exaggerating their benefits.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="artificial-intelligence" /><category term="llm" /><summary type="html">If you follow me on social media, you know I am deeply sceptical about the benefits of AI in most usecases. I am critical of the environmental impact, I am worried about the centralization on a few SaaS providers and I am sad about the topic dominating every product roadmap, using up ressources which could have been used to progress the products exiting features further. On the other hand I talked about this topic with a lot of smart people this year – and many praised large language models and told me how they made their daily work easier. I’m writing this post, to reflect about my opinion and how arrogant it would be to dismiss peoples first hand experience without second guessing myself. If everybody seems to be a wrong-way driver, it is probably a good idea to check if you are yourself not the one going the wrong way.</summary></entry><entry><title type="html">Rock Stars are male and old - Fun with data</title><link href="https://javahippie.net/data/2024/03/23/rock-stars-are-male-and-old.html" rel="alternate" type="text/html" title="Rock Stars are male and old - Fun with data" /><published>2024-03-23T19:00:00+00:00</published><updated>2024-03-23T19:00:00+00:00</updated><id>https://javahippie.net/data/2024/03/23/rock-stars-are-male-and-old</id><content type="html" xml:base="https://javahippie.net/data/2024/03/23/rock-stars-are-male-and-old.html">&lt;p&gt;This week I read &lt;a href=&quot;https://www.visions.de/features/kolumnen-und-rubriken/rage-against-the-patriarchy/rage-against-the-patriarchy-altersdiskriminierung/&quot;&gt;a (German) article&lt;/a&gt; about female rock musicians age discrimination.
It made me wonder, how the lineup for the 2024 edition of the German “Rock Am Ring” festival compares.&lt;/p&gt;

&lt;p&gt;The repository with all the data and scripts mentioned &lt;a href=&quot;https://github.com/javahippie/rar-bands/tree/main&quot;&gt;can be found here&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;collecting-the-data&quot;&gt;Collecting the data&lt;/h2&gt;
&lt;p&gt;The first and most important step was also the most boring one: Collecting the data. Next to the name of the band, 
founding year and if they are a headliner or sub-headliner. The founding year does not carry that much significance,
as the lineup also contains some super groups or solo acts of people who are well known for their older bands. Kerry King,
known as the guitarist for Slayer, started his solo project last year, but nobody would call him a young and upcoming artist.&lt;/p&gt;

&lt;p&gt;I also tried to identify the “main person”. This is usually the singer, but I would argue that for “Kerry King” the main 
person is… well… Kerry King on the guitar and not their singer. From 66 bands in total, 9 have “main people” who are 
not male.&lt;/p&gt;

&lt;p&gt;The most tricky part was collecting the year of 
birth for the bands main person. Bigger acts tend to have their own Wikipedia page with a birthdate, but the smaller
the band and the younger the members become, the harder it is to find this information. This might be even worse for the 
question I am trying to answer: How is the age distribution? If the birthyear is missing, it might be missing especially
for younger, smaller acts. In the end, I managed to find the birthyear of all “main persons” but 7. If any of the readers
might know about the birth date of the following people, I’d be really grateful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Oliver Meister (Betontod)&lt;/li&gt;
  &lt;li&gt;Josh Doty (Cemetery Sun)&lt;/li&gt;
  &lt;li&gt;James Joseph (James And The Cold Gun)&lt;/li&gt;
  &lt;li&gt;Simon Klemp (Schimmerling)&lt;/li&gt;
  &lt;li&gt;Timo Warkus (Team Scheisse)&lt;/li&gt;
  &lt;li&gt;Delila Paz (The Last Internationale)&lt;/li&gt;
  &lt;li&gt;Jordan O’Leary (The Scratch)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For two of the birth years I needed to estimate. I assumed that Ashrita Kumar (Pinkshift) started her Bachelors Degree
at 18, and Alex Taylor (Malevolence) stated in an interview, that he wrote the songs for the bands debut album when 
he was 18 or 19.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20240323/table_header.png&quot; alt=&quot;View of the Excel header, containng Band name headliner status founding year, age of the band, also the main person, their birth year, age and gender&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;loading-the-data&quot;&gt;Loading the data&lt;/h2&gt;
&lt;p&gt;I entered all the data into an Excel list, because Excel is a really convenient tool for this. To aggregate or visualize
the data, I needed a better tool, preferrably SQL-based. As I saw DuckDB mentioned online a lot in the last months (thanks, Michael!),
I was happy to have a reason to try it out. In the CLI, I installed and loaded the extension to access Excel files and
created a new Table from the file:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;INSTALL&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spatial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LOAD&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;spatial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bands&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ST_Read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'RaR Bands.xlsx'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From this starting point, I started to explore the data:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;select median(main_person_age) from bands;
┌─────────────────────────┐
│ median(main_person_age) │
│         double          │
├─────────────────────────┤
│                    42.0 │
└─────────────────────────┘

select median(main_person_age) from bands where main_person_gender &amp;lt;&amp;gt; 'male';
┌─────────────────────────┐
│ median(main_person_age) │
│         double          │
├─────────────────────────┤
│                    27.0 │
└─────────────────────────┘

select median(main_person_age) from bands where headliner = 'true';
┌─────────────────────────┐
│ median(main_person_age) │
│         double          │
├─────────────────────────┤
│                    53.0 │
└─────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No surprises here: The median age of the main person is 42, but only 27 for non-male ones. The median age of the 
headlining-acts main-people is even 53. Rock Music, the rebellious sound of the youth!&lt;/p&gt;

&lt;h2 id=&quot;visualizing-the-data&quot;&gt;Visualizing the data&lt;/h2&gt;
&lt;p&gt;In addition to seeing the numbers, I wanted to visualize the age distribution. My tool of choice for this is R, because
a) I read a book on it once and b) my wife is a data scientist, and she can help me with R if I don’t know what to do. 
Luckily, there is a DuckDB package for R, and it works quite well. Data wrangling in R always felt a little weird to me,
although I really liked the Tidyverse. Having the ability to filter with SQL however fits my experience as a software
developer much better. I managed to create the following script, which loads the Excel sheet into DuckDB, queries the
DB for two datasets and plots them with ggplot2:&lt;/p&gt;

&lt;div class=&quot;language-R highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;DBI&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ggplot2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;library&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;duckdb&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dbConnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duckdb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;duckdb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;:memory:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dbExecute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;INSTALL spatial&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dbExecute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;LOAD spatial&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dbExecute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;CREATE TABLE bands AS SELECT * FROM ST_Read('RaR Bands.xlsx')&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bands&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dbGetQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;con&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;SELECT * FROM bands&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ggplot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bands&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;aes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main_person_age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fill&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;main_person_gender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;geom_histogram&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first three lines of the script load the packages we need. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DBI&lt;/code&gt; is a database interface, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ggplot2&lt;/code&gt; is the library
we will use for plotting, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;duckdb&lt;/code&gt; is, you might have guessed it, for DuckDB. We start a DuckDB in-memory instance
with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dbConnect(duckdb::duckdb(), &quot;:memory:&quot;)&lt;/code&gt;. On this connection, we can then operate, install and load the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spatial&lt;/code&gt;
extension and create a table from the Excel sheet, like we saw earlier. We select the whole data into the vector &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bands&lt;/code&gt;.
(Yes, loading the data directly into a dataframe would have been easier, but please let me use my new, shiny, duck shaped tool).&lt;/p&gt;

&lt;p&gt;After loading the data, we want to plot it: We create a new plot from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bands&lt;/code&gt;, put &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main_person_age&lt;/code&gt; on the x-axis and
fill the data according to the value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main_person_gender&lt;/code&gt;. We would like to plot a histogram to see the distribution.
The result:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20240323/plot.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The data is really not that large or interesting, but for this year’s RaR edition we can see, that the prejudice exists.
Male artists are not only more dominant, they are also older - and if we conclude that the headliners are the more 
successful and admired bands, they are even older. Obviously this is only looking at one special event and how the
organizers selected the acts for the lineup, but I thought this little experiment to be interesting nevertheless.&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="data" /><summary type="html">This week I read a (German) article about female rock musicians age discrimination. It made me wonder, how the lineup for the 2024 edition of the German “Rock Am Ring” festival compares.</summary></entry><entry><title type="html">Work like nobody is watching</title><link href="https://javahippie.net/culture/2023/06/02/work-like-nobody-is-watching.html" rel="alternate" type="text/html" title="Work like nobody is watching" /><published>2023-06-02T15:00:00+00:00</published><updated>2023-06-02T15:00:00+00:00</updated><id>https://javahippie.net/culture/2023/06/02/work-like-nobody-is-watching</id><content type="html" xml:base="https://javahippie.net/culture/2023/06/02/work-like-nobody-is-watching.html">&lt;p&gt;In the last months (I started this as a draft in February) there were a lot of news in the tech industry. While most of them were related to layoffs or personal
drama about the owners of social media companies, there was also a lot of coverage about AI. After OpenAI made a big
impression with ChatGPT and the announcement to collaborate with Bing, Google announced their own AI “Bard” and rushed
to beat Microsofts AI demo with their own
announcement. &lt;a href=&quot;https://www.theverge.com/2023/2/8/23590864/google-ai-chatbot-bard-mistake-error-exoplanet-demo&quot;&gt;Which did not exactly go well.&lt;/a&gt;.
But don’t worry, this is not another post about AI. This post is about expectations other people have about our work,
and how they impact what we do in our tech jobs.&lt;/p&gt;

&lt;h2 id=&quot;are-your-decisions-at-work-technical-or-driven-by-expectations&quot;&gt;Are your decisions at work technical, or driven by expectations?&lt;/h2&gt;

&lt;p&gt;Software Architecture can be seen as a set of decisions which are made about the way our software is structured and the
approaches it uses. In an ideal world, these decisions are purely technical and made for the benefit of our application.
In the real world however, they are often heavily influenced by external expectations. While some of these expectations
might be justified, there are also expectations which are more rooted in social pressure: Developers want to use new
technology,
so why not build the Frontend with that new JS Framework…? This will also make it easier to hire new developers, so
it’s not only an expectation of the developers, but also of HR. Also, by showing our shareholders that we are eager to
modernize our stack, we can reassure them that we are at the front of modern tech!&lt;/p&gt;

&lt;h2 id=&quot;existing-expectations-of-shareholders&quot;&gt;Existing expectations of shareholders&lt;/h2&gt;

&lt;p&gt;Before looking at the topic with a focus on our day jobs, let take a look at the big picture which gave me the idea,
first. If your company is publicly traded, you will have to accept that your shareholders will have expectations on how
you manage your company. They want to be sure that the shares they hold will be more valuable in the future and want the
managers of the company to prioritize the shareholder value. Some might argue that the worth of a company at a stock
exchange is tied to the actual value and prospects of the company, but in my opinion the correlation is not that easy. A
lot of the market value is influenced by public image and perception. People don’t buy shares in a company because they
believe it is valuable now, but they believe that it will be more valuable in the future. This belief needs to be upheld
and fed by management at all cost. This is why speeches, articles and public statements of CEOs always aim at
shareholders and the public, even if they address the employees on the surface.&lt;/p&gt;

&lt;p&gt;From an economic perspective, it makes sense to satisfy the expectations of shareholders. They are the people and
companies providing the capital which the companies need to work, after all. In the case of mass layoffs,
&lt;a href=&quot;https://www.gsb.stanford.edu/insights/why-copycat-layoffs-wont-help-tech-companies-or-their-employees&quot;&gt;which might not really “save money”&lt;/a&gt;,
it is also helpful to take a look at these expectations. There seems to be a pattern which
shows, &lt;a href=&quot;https://www.forbes.com/sites/jonathanponciano/2023/01/23/spotify-alphabet-and-meta-lead-tech-stock-surge-after-massive-layoff-announcements/&quot;&gt;that stock prices for companies who announce layoffs rise.&lt;/a&gt;
This seems to be counterintuitive: Are layoffs not a signal that the company is not doing well? Won’t the severance
packages and benefits which have to be paid cost a lof of money? That the management which is currently in charge has
misjudged the market, over hired and wasted the shareholders’ money? Well, yes and no. Another way to read the
layoffs and the attached press statements is: “We have made mistakes in the past, but we have now taken measures to make
sure that the shareholder value will go up in the future”. It is a message saying “we see your expectations and acted
accordingly”. This fits the fact, that a major investor pushed Google to lay off even more
people &lt;a href=&quot;https://www.theguardian.com/technology/2022/nov/15/major-investor-calls-on-google-owner-to-aggressively-cut-staff-and-pay&quot;&gt;in a statement&lt;/a&gt;.
The shareholders see layoffs at other tech companies, see that the price of their stock surges, and wonder why
management is not doing the same. It is an economic decision, but not one that is tied to the actual business the
company is in.&lt;/p&gt;

&lt;h2 id=&quot;satisfying-the-expectations&quot;&gt;Satisfying the expectations&lt;/h2&gt;

&lt;p&gt;The gap between “decisions made with shareholders in mind” and “decisions made with the customers / business in mind”
was very clear with the announcement of Google Bard. Microsoft announced the collaboration with ChatGPT earlier,
&lt;a href=&quot;https://www.theverge.com/2023/2/3/23584675/microsoft-ai-bing-chatgpt-screenshots-leak&quot;&gt;leaked a short preview&lt;/a&gt; showing
that the integration was surprisingly far ahead. This created the impression, that Microsoft was investing heavily in
Bing, equipping it with AI and working on the next generation of search engines after Bing was more or less failing for
over a decade. This could only be perceived as a threat for Google Search, which is assumed to have ~92% market share of
all search engines. Even worse for Google, it raised fear among shareholders, that the worth of the Alphabet stock might
decrease in the future. Google had to act to counter these
fears, &lt;a href=&quot;https://blog.google/technology/ai/bard-google-ai-search-updates/&quot;&gt;and announced their own AI, Bard&lt;/a&gt;. They had to
let their shareholders know, that they were not far behind Bing and OpenAI, and had to let them know quickly. They tried
to beat Microsofts press conference in order to take some attention away from them. Unfortunately, by rushing the
decision &lt;a href=&quot;https://www.theverge.com/2023/2/8/23590864/google-ai-chatbot-bard-mistake-error-exoplanet-dem&quot;&gt;they showed the AI making a factual error&lt;/a&gt;
in a promotional
video, &lt;a href=&quot;https://www.reuters.com/technology/google-ai-chatbot-bard-offers-inaccurate-information-company-ad-2023-02-08/&quot;&gt;which crashed their stock by 9% and reduced their perceived worth by 100 billion dollars.&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;smaller-expectations&quot;&gt;Smaller expectations&lt;/h2&gt;

&lt;p&gt;Most people won’t have to communicate to shareholders in their day-to-day job, but I find it helpful to reflect in
regular intervals if the decisions we take are influenced by the subject, or if external expectations are playing a
part, too. Do we use React because it is the best fit, or I believe that I won’t find developers when I cannot tell them
about our hip tech stack at the next conference? Do I run my application in the cloud because it is the easiest or most
cost-effective way to do it, or does the C-level require it to signal the modern infrastructure to the investors? Do I
port my small-ish application to Quarkus on GraalVM Native Image because I need the startup time, or do I want to brag
about it to fellow developers and tie my self-worth to the fact that my tech-stack is very recent? And what is the cost
of every of these decisions to the use case I am really trying to solve?&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="culture" /><summary type="html">In the last months (I started this as a draft in February) there were a lot of news in the tech industry. While most of them were related to layoffs or personal drama about the owners of social media companies, there was also a lot of coverage about AI. After OpenAI made a big impression with ChatGPT and the announcement to collaborate with Bing, Google announced their own AI “Bard” and rushed to beat Microsofts AI demo with their own announcement. Which did not exactly go well.. But don’t worry, this is not another post about AI. This post is about expectations other people have about our work, and how they impact what we do in our tech jobs.</summary></entry><entry><title type="html">Clickbaiting Mastodon instances</title><link href="https://javahippie.net/clojure/mastodon/2022/12/18/clickbait.html" rel="alternate" type="text/html" title="Clickbaiting Mastodon instances" /><published>2022-12-18T11:00:00+00:00</published><updated>2022-12-18T11:00:00+00:00</updated><id>https://javahippie.net/clojure/mastodon/2022/12/18/clickbait</id><content type="html" xml:base="https://javahippie.net/clojure/mastodon/2022/12/18/clickbait.html">&lt;p&gt;If you visit this site, it is possible that you fell for some clickbait! No worries, this is just a small script I
wrote for myself for fun. It makes use of the fact, that each Mastodon instance generates its own HTTP preview, so the
preview you saw in your client contained a headline which mentioned the Mastodon instance you are on.&lt;/p&gt;

&lt;h2 id=&quot;how-does-this-work&quot;&gt;How does this work?&lt;/h2&gt;

&lt;p&gt;When a post which contains a link gets federated to a mastodon instance, the instance will query the address which is
contained and generate a web preview. This also happens for private messages (or restricted posts), so don’t toot any
links which contain credentials. Even better, don’t use such links ever!&lt;/p&gt;

&lt;p&gt;When the Mastodon server creates the preview, it tries to be a good citizen of the web and uses an expressive User Agent
string. In the case of the instance I am on, it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http.rb/5.1.0 (Mastodon/4.0.2; +https://freiburg.social/) Bot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It contains the Ruby version, the Mastodon version &lt;em&gt;and the hostname of the instance&lt;/em&gt;. We can play around with this and
generate a web preview which is tailored to the instance itself. Every user will see the preview mentioning their
instance:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20221218/img.png&quot; alt=&quot;img.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you click on the link, though, and your user agent is not recognized as a Mastodon instance, you will be redirected
to this blog right here.&lt;/p&gt;

&lt;h2 id=&quot;wheres-the-code&quot;&gt;Where’s the code?&lt;/h2&gt;

&lt;p&gt;Glad you asked! Here is the small babashka script which hosts the logic. :&lt;/p&gt;

&lt;div class=&quot;language-clojure highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ns&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;core&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:require&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org.httpkit.server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;srv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hiccup2.core&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:refer&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:import&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;java.time&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;logged&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user-agent-string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;spit&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user-agents.log&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user-agent-string&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.toString&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;LocalDateTime/now&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:append&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user-agent-string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extract-instance-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user-agent-string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;re-find&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;\((.*).?\;.?\+https:\/\/(.*)\/\)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user-agent-string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
           &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;and&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.contains&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Mastodon&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extract-client-info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:keys&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]}]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extract-instance-name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;logged&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user-agent&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;defn&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;handle-request&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;extract-client-info&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
           &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bait&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Why &quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot; is the best Mastodon instance to be on!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:headers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;content-type&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:body&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                   &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:html&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                               &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:head&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:title&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                               &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:body&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:h1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bait&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]]))})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
             &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:status&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;  &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;301&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
              &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:headers&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Location&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://javahippie.net/clojure/mastodon/2022/12/18/clickbait.html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}})))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;srv/run-server&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;'handle-request&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;:port&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The code is also &lt;a href=&quot;https://github.com/javahippie/clickbait&quot;&gt;on GitHub&lt;/a&gt;&lt;/p&gt;</content><author><name>Tim Zöller</name></author><category term="clojure" /><category term="mastodon" /><summary type="html">If you visit this site, it is possible that you fell for some clickbait! No worries, this is just a small script I wrote for myself for fun. It makes use of the fact, that each Mastodon instance generates its own HTTP preview, so the preview you saw in your client contained a headline which mentioned the Mastodon instance you are on.</summary></entry></feed>