<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: marian-varga</title>
    <description>The latest articles on DEV Community by marian-varga (@marianvarga).</description>
    <link>https://dev.to/marianvarga</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1063692%2F3839617d-1e5f-446f-b4e9-86015f93a400.jpg</url>
      <title>DEV Community: marian-varga</title>
      <link>https://dev.to/marianvarga</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marianvarga"/>
    <language>en</language>
    <item>
      <title>JSON Schema default that complicates compatibility checks</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Fri, 10 Apr 2026 07:19:54 +0000</pubDate>
      <link>https://dev.to/marianvarga/json-schema-default-that-complicates-compatibility-checks-32e</link>
      <guid>https://dev.to/marianvarga/json-schema-default-that-complicates-compatibility-checks-32e</guid>
      <description>&lt;p&gt;Automating API contract checking can save you from human mistakes. The syntax part of API contracts is a low-hanging fruit that can be improved quickly by centralising the schemas on a schema registry.&lt;/p&gt;

&lt;p&gt;Schema registries not only store the schemas, they can also perform automated compatibility checks on them.&lt;/p&gt;

&lt;p&gt;Talking about API integrations, a bullet-proof, yet practical, definition of what is a non-breaking (compatible) change vs breaking (incompatible) is not trivial. It does not involve just the provider/owner of the API, but also all its consumers. For some consumers the change may be a breaking one, for others not.&lt;/p&gt;

&lt;p&gt;JSON is very popular because it is simple and because JSON documents can be read by humans directly. But when it comes to automating schema compatibility checks, it shows some weaknesses.&lt;/p&gt;

&lt;p&gt;Yes, you can tame the JSON data sent over your APIs to some degree using OpenAPI or AsyncAPI.&lt;/p&gt;

&lt;p&gt;Let's take the &lt;a href="https://petstore3.swagger.io/api/v3/openapi.json" rel="noopener noreferrer"&gt;Petstore&lt;/a&gt; example. If you pretty print the OpenAPI spec and scroll down to the section starting with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;components"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schemas"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can find JSON schemas like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int64"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
                &lt;span class="pi"&gt;},&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dogs"&lt;/span&gt;
                &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Based on this you can tell that &lt;code&gt;{ "id": 1234 }&lt;/code&gt; is valid, but &lt;code&gt;{ "id": "1234" }&lt;/code&gt; (using a JSON string instead of the number) is invalid.&lt;/p&gt;

&lt;p&gt;However, what the schema does not tell explicitly is, that a document with any extra fields is valid, too.&lt;/p&gt;

&lt;p&gt;In this document&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my category"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"newAwesomeField"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added the "newAwesomeField" that is not specified by the schema, but any application that reads the JSON document should accept it. This usually works because deserializers like Jackson ignore unknown fields by default.&lt;/p&gt;

&lt;p&gt;Actually, the above JSON Schema is equivalent to this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int64"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
                &lt;span class="pi"&gt;},&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Dogs"&lt;/span&gt;
                &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="pi"&gt;},&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;additionalProperties"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "additionalProperties" JSON Schema attribute is commonly omitted. Its default value is true, meaning that documents with any properties on top of those explicitly listed are valid.&lt;/p&gt;

&lt;p&gt;Now what if someone decides to create a new version of the schema, making the "newAwesomeField" official? Let's say the field is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;newAwesomeField"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Some&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;awesome&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;free&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;text"&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the document &lt;code&gt;{ "id": 1234, "name": "my category", "newAwesomeField": true }&lt;/code&gt; that was valid with the old schema becomes invalid! It is because the new schema defines newAwesomeField as a string, whereas in the document we use it with a boolean value.&lt;/p&gt;

&lt;p&gt;This default openness of JSON Schema creates a barrier for a safe evolution of schemas.&lt;/p&gt;

&lt;p&gt;The other two commonly used schema languages (Avro and Protobuf) are stricter, making the schema evolution with them more controlled. It makes sense for them because they are designed for efficient binary serialization. Too much flexibility (including for example that you can shuffle JSON fields in any order) is not good for efficient processing.&lt;/p&gt;

&lt;p&gt;If you are using JSON in your API payloads and want to make compatibility checks safer, it may be a good idea to include &lt;code&gt;"additionalProperties": false&lt;/code&gt; in your JSON Schemas.&lt;/p&gt;

&lt;p&gt;If I triggered your interest, you can read &lt;a href="https://json-schema.org/understanding-json-schema/reference/object#additionalproperties" rel="noopener noreferrer"&gt;all the details on how the additionalProperties work&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>asyncapi</category>
      <category>schema</category>
    </item>
    <item>
      <title>Don't kill the bearer of bad news</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Mon, 15 Dec 2025 18:15:39 +0000</pubDate>
      <link>https://dev.to/marianvarga/dont-kill-the-bearer-of-bad-news-45p9</link>
      <guid>https://dev.to/marianvarga/dont-kill-the-bearer-of-bad-news-45p9</guid>
      <description>&lt;p&gt;Have you ever been in a situation where you had to manage expectations of people who thought a software project was on track and delivering the required features would be easy? Having to explain technical debt, gaps in feature specifications and risky external dependencies?&lt;/p&gt;

&lt;p&gt;It is scary to tell the unpleasant truth. You don't want to sound negative, decrease the team morale and make managers angry. And there might be managers or customers who will dislike people telling them such not-so-nice news. On the other hand, staying silent about problems and risks won't make them disappear. It is more likely they will only grow and suddenly blow up when it is too late to adjust plans and mitigate them.&lt;/p&gt;

&lt;p&gt;People telling bad news early and accurately are important. And so are parts of the system doing the same.&lt;/p&gt;

&lt;p&gt;In microservices architectures and many other real-world systems that have to be distributed it is common that a service, in order to execute a request, calls APIs of another service or services.&lt;/p&gt;

&lt;p&gt;Some architectures even contain dedicated services whose purpose is to translate the communication between two parts of the system, typically between a legacy application and new services or between in-house systems and external (hard-to-influence) services. Such services are often called a gateway, anti-corruption layer, adapter or facade.&lt;/p&gt;

&lt;p&gt;It is not enough if these services can translate the data in positive scenarios. If something is not right, forwarding it as-is to the other side or even worse, ignoring it, can lead to issues that are hard to detect and fix.&lt;/p&gt;

&lt;p&gt;Imagine a service A calling service B that in turn calls service C using a REST API. What should service B do if service C returns an HTTP 4xx status? Just forwarding the same error to A and/or writing it in a log using a generic exception handler won't do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------+        REST       +-----------+        REST       +-----------+
| Service A | ---------------&amp;gt;  | Service B | ---------------&amp;gt;  | Service C |
+-----------+        ??? &amp;lt;----  +-----------+     HTTP 4xx &amp;lt;--  +-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Service B is supposed to translate between the worlds of C and A. It probably adds some more data and context when calling C, so C's response may make no sense to A and will be unusable to fix the problem.&lt;/p&gt;

&lt;p&gt;4xx statuses in HTTP mean client error. But can we be sure it is A's fault if C returned a 4xx error? Service B should expect the most probable errors returned by C, detect them and translate them to responses that A understands.&lt;/p&gt;

&lt;p&gt;For errors returned by C that B does not expect, it is not clear if they are caused by A or B, so to be on the safe side, it may be more correct to return a 500 (internal server error) to A because it is an error that B failed to identify.&lt;/p&gt;

&lt;p&gt;Also, B should validate the data in the request from A. In case the data cannot be used to create a valid request for C, it should not even make the call to C and return a meaningful validation error to A. Skipping the validation may lead to strange hard-to-understand errors coming from C and forwarded to A or written to logs.&lt;/p&gt;

&lt;p&gt;Correct error handling and logging at all levels of a software system is essential to prevent bad things from becoming even worse. Especially in distributed systems where it is very common that the original context is lost.&lt;/p&gt;

</description>
      <category>management</category>
      <category>leadership</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Why did we disable CSRF protection?</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Mon, 01 Sep 2025 13:04:45 +0000</pubDate>
      <link>https://dev.to/marianvarga/why-did-we-disable-csrf-protection-2bni</link>
      <guid>https://dev.to/marianvarga/why-did-we-disable-csrf-protection-2bni</guid>
      <description>&lt;p&gt;All your services / applications and the whole solution may be working from the functional perspective, however there are still many things to do before you can go to production. One such activity is making sure that there are no security vulnerabilities.&lt;/p&gt;

&lt;p&gt;Suddenly, a colleague of yours is asking: Why do we have this&lt;/p&gt;

&lt;p&gt;httpSecurity.csrf((csrf) -&amp;gt; csrf.disable());&lt;br&gt;
here??? We need to be protected against everything, including CSRF, right?&lt;/p&gt;

&lt;p&gt;(This is from &lt;a href="https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#disable-csrf" rel="noopener noreferrer"&gt;Spring&lt;/a&gt; but you may see something similar with other frameworks.)&lt;/p&gt;

&lt;p&gt;You start scratching your head trying to remember what CSRF actually is... Or you know immediately because you've read this post :)&lt;/p&gt;

&lt;p&gt;Imagine two people, Alice and Eve, working with their computers, sitting next to each other. Eve is evil, trying to attack Alice, but to not show it, she will never look at Alice's keyboard or display. Instead, while Alice goes for a tea, Eve will plug her own keyboard in Alice's USB port.&lt;/p&gt;

&lt;p&gt;When Alice comes back and unlocks her machine, Eve can type something evil that will be executed in Alice's name, like "send money to this account" or "drop the database". Of course, Eve has to guess the moment to type when Alice puts focus on the right window...&lt;/p&gt;

&lt;p&gt;The attack works because Alice's machine can't tell that it is not Alice, but somebody else typing.&lt;/p&gt;

&lt;p&gt;CSRF means Cross-Site Request Forgery; instead of keyboards connected to the same computer we have HTTP requests sent from the same browser.&lt;/p&gt;

&lt;p&gt;One of the requests is a legitimate login to a web application, e.g. internet banking, resulting in a session cookie being stored by the browser. Another one is a request from a malicious website that looks exactly the same as a legitimate request to send money.&lt;/p&gt;

&lt;p&gt;The session cookie is added to the request automatically by the browser, so the request is authenticated and will be executed by the server!&lt;/p&gt;

&lt;p&gt;How to prevent CSRF? We have to make sure that the sensitive requests cannot be guessed (excluding the automated cookie appended by the browser). So we need something dynamic, random, added to the request explicitly.&lt;/p&gt;

&lt;p&gt;The CSRF protection in Spring (or another framework) checks an additional non-cookie parameter in the HTTP request.&lt;/p&gt;

&lt;p&gt;Luckily, well-designed REST APIs usually don't rely on session cookies, they are stateless. That includes authentication where the preferred method is Open ID Connect (OAuth). An authentication token (in the JWT format) is sent in the "Authorization" header with every request. You can learn more about securing REST APIs in our book (see &lt;a href="https://love2integrate.com" rel="noopener noreferrer"&gt;love2integrate.com&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So, you can reassure your colleague that with stateless APIs that don't use cookie authentication it is OK to have the CSRF protection turned off.&lt;/p&gt;

&lt;p&gt;See also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://security.stackexchange.com/questions/170388/do-i-need-csrf-token-if-im-using-bearer-jwt" rel="noopener noreferrer"&gt;Do I need CSRF token if I'm using Bearer JWT?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://security.stackexchange.com/questions/166724/should-i-use-csrf-protection-on-rest-api-endpoints/166798#166798" rel="noopener noreferrer"&gt;Should I use CSRF protection on Rest API endpoints?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BTW, why did we say that Eve was constrained not to look at Alice's screen? Well, CSRF is a write-only attack. Reading responses is blocked thanks to the "same-origin policy", a protection that browsers implemented a long time ago: &lt;a href="https://en.wikipedia.org/wiki/Same-origin_policy" rel="noopener noreferrer"&gt;Same-origin policy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, with complex JavaScript frontend frameworks and content delivery networks it may be necessary to relax the same-origin policy. For that we need to know another web security setting, CORS (Cross-Origin Resource Sharing). If you are interested, I can write about that another time.&lt;/p&gt;

</description>
      <category>web</category>
      <category>restapi</category>
      <category>security</category>
      <category>java</category>
    </item>
    <item>
      <title>Can an application suffer from jet lag?</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Tue, 08 Jul 2025 04:23:40 +0000</pubDate>
      <link>https://dev.to/marianvarga/can-an-application-suffer-from-jet-lag-30o9</link>
      <guid>https://dev.to/marianvarga/can-an-application-suffer-from-jet-lag-30o9</guid>
      <description>&lt;p&gt;When designing APIs and data models, I prefer simplicity and avoid ambiguity.&lt;/p&gt;

&lt;p&gt;For date and time, programmers like using epoch time. This is the number of seconds or milliseconds since January 1, 1970. It requires just one integer field, making it easy to compare and manipulate.&lt;/p&gt;

&lt;p&gt;My favourite geek joke is that the world should switch from measuring time in hours and months to using megaseconds and gigaseconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  We accept UTC only?
&lt;/h2&gt;

&lt;p&gt;APIs are often crafted by backend developers who aim for simplicity. Sending epoch times as integers might be harsh. However, even if using the more human-readable ISO string format (YYYY-MM-DDTHH....), requiring all time fields to be in &lt;a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time" rel="noopener noreferrer"&gt;UTC&lt;/a&gt; is just shifting responsibility onto others.&lt;/p&gt;

&lt;p&gt;Relying on the client to handle time zone conversion can lead to issues, especially if the time is entered by users via a GUI.&lt;/p&gt;

&lt;p&gt;Even if your backend only works in a single timezone, your users might be in different time zones.&lt;/p&gt;

&lt;p&gt;People think in local time, not UTC. If the frontend provides UTC time, it drops the timezone information. Later, when displaying stored UTC time, the frontend must guess the timezone. An incorrect guess can confuse users.&lt;/p&gt;

&lt;p&gt;A client device's timezone setting might be wrong, so your application shouldn't depend on it. You might want to show the assumed timezone on the GUI or let users change it if needed.&lt;/p&gt;

&lt;p&gt;Defining the API time field as date and time with timezone (not limited to UTC) prevents information loss. The Java &lt;a href="https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/time/ZonedDateTime.html" rel="noopener noreferrer"&gt;ZonedDateTime&lt;/a&gt; class models this well.&lt;/p&gt;

&lt;p&gt;However, this adds complexity, especially in the persistence layer. Your database might not support datetime with timezone in one field. You may need two fields: one for UTC time for easy sorting and another for the timezone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Date without time = easy?
&lt;/h2&gt;

&lt;p&gt;Business needs often deal with dates that don’t include a time component. For example, a subscription may be active from day X until midnight of day Y.&lt;/p&gt;

&lt;p&gt;But don’t remove the time information from your APIs without careful thought. Timezone differences can make day Y in Europe become day Y+1 in Australia. (I enjoy celebrating the Australian New Year so I can sleep at normal hours. I know, I’m old. And I have kids.)&lt;/p&gt;

&lt;p&gt;So, ask the business what timezone the date ranges apply to. Double-check if you need the time component with the date inputs!&lt;/p&gt;

</description>
      <category>api</category>
      <category>java</category>
    </item>
    <item>
      <title>API killed the transaction star</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Mon, 30 Jun 2025 14:24:34 +0000</pubDate>
      <link>https://dev.to/marianvarga/api-killed-the-transaction-star-2h18</link>
      <guid>https://dev.to/marianvarga/api-killed-the-transaction-star-2h18</guid>
      <description>&lt;p&gt;Building applications from various services is common today. This can mean using a microservices architecture, which allows teams to develop and deploy features quickly. It may also involve integrating legacy systems with new ones or connecting external services to in-house systems.&lt;/p&gt;

&lt;p&gt;However, distributed systems come with challenges. One major issue is that we can't depend on transactions like we did with centralized applications. Transactions provide consistency. They ensure that changes across multiple database tables either succeed together or fail together, keeping data consistent.&lt;/p&gt;

&lt;p&gt;In distributed systems, achieving this level of consistency is difficult. Data may be stored across multiple databases linked by remote APIs. We can, however, aim for "eventual consistency." This means we accept temporary inconsistencies but expect them to resolve automatically.&lt;/p&gt;

&lt;p&gt;If you need to combine a transactional resource (like a database) with one non-transactional resource (like a remote API), you might try these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start a transaction.&lt;/li&gt;
&lt;li&gt;Apply changes to the database.&lt;/li&gt;
&lt;li&gt;Call the remote API.&lt;/li&gt;
&lt;li&gt;Commit the transaction if the API call is successful; otherwise, roll back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach has drawbacks. Step 4 may fail, or the service might crash (e.g., due to Kubernetes) before reaching it. Additionally, the time spent calling the remote API adds to the transaction time. Long transactions lock objects for a long time, leading to performance issues or deadlocks.&lt;/p&gt;

&lt;p&gt;If you have multiple non-transactional resources to call, this simple method won't work.&lt;/p&gt;

&lt;p&gt;A more complex solution is using compensating transactions. Here, you commit transactions for the transactional resources right away (or don't use transactions at all). If a non-transactional operation fails, you must reverse the changes made so far.&lt;/p&gt;

&lt;p&gt;Picture booking a summer holiday deal but needing manager approval. If your manager declines, you'll need to cancel your booking. This cancellation is your compensating transaction, but the holiday has to be cancellable.&lt;/p&gt;

&lt;p&gt;If you're updating a large, complex resource, it can be tough to identify what parts you're changing and how to reverse those changes. If another update occurs simultaneously, conflicts can arise. For example, if you are raising a price from 10 to 15, but meanwhile another update sets it to 12, should you revert to 10 or 12?&lt;/p&gt;

&lt;p&gt;The most resilient distributed systems tend to be event-based. Each state change in a service is an irreversible fact, communicated through an event message. Consumers of these events must respond accordingly to maintain consistency.&lt;/p&gt;

&lt;p&gt;To ensure the data in the service database aligns with the events sent, we often use the Outbox pattern. First, you make changes to the service database within a transaction and then insert a record in an "outbox" table for the event. Transactions do remain useful in distributed architectures.&lt;/p&gt;

&lt;p&gt;The outbox table is checked regularly, and messages are sent via messaging infrastructure (like Kafka). If a service restarts, the same event might be sent twice.&lt;/p&gt;

&lt;p&gt;Thus, event consumers must be idempotent. If they receive a duplicate event, the outcome should be the same as if they received it just once.&lt;/p&gt;

&lt;p&gt;What has been your experience with managing consistency in distributed systems?&lt;/p&gt;

</description>
      <category>api</category>
      <category>database</category>
    </item>
    <item>
      <title>Should we echo POST and PUT requests?</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Tue, 24 Jun 2025 13:39:30 +0000</pubDate>
      <link>https://dev.to/marianvarga/should-we-echo-post-and-put-requests-22ik</link>
      <guid>https://dev.to/marianvarga/should-we-echo-post-and-put-requests-22ik</guid>
      <description>&lt;p&gt;There is a common REST API design practice developers often apply without much thinking.&lt;/p&gt;

&lt;p&gt;When implementing a POST or PUT endpoint, we usually return the created or updated entity in the HTTP response body.&lt;/p&gt;

&lt;p&gt;I am not sure about the origin of this convention, but I suspect it can be related to the urge to reuse data models: We already have a model for the entity that is used in the request and in the GET response, so why not use it also for the POST and PUT responses? I once wrote a &lt;a href="https://dev.to/marianvarga/what-is-a-url-4jfn"&gt;blog post&lt;/a&gt; where I show how reusing too much can lead to an incorrect API design.&lt;/p&gt;

&lt;p&gt;There is nothing in the HTTP protocol or REST architecture telling us to echo back what we got in a POST or PUT request. On the contrary, there are some arguments against it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;waste of network bandwidth and increase of response time, especially for large entities&lt;/li&gt;
&lt;li&gt;the client already has the data because it has just sent them in the request&lt;/li&gt;
&lt;li&gt;it can lead to incorrect assumptions on the client side that the returned data are always up-to-date, but that might not be true if some other request on the same entity is performed in parallel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Someone pointed out that there can be some "additional processing" happening on the server and therefore it may be necessary to return the entity after that processing. For sure, if a new entity with a new ID is created in a POST request, we should return the URL of the new resource (or at least its ID).&lt;/p&gt;

&lt;p&gt;But even if there is some more information generated on the server, why do we automatically assume that the client needs the modified parts or the whole entity data? It goes against the single responsibility and CQRS (Command-Query Responsibility Segregation) principle: a POST or PUT is expected to do a create/update. If the client needs to read the data, it can use a GET request.&lt;/p&gt;

&lt;p&gt;Remember, APIs, like all interfaces, should be minimal. Only expose what needs to be exposed. Adding stuff to an API data model is easy (non-breaking), but removing it is hard.&lt;/p&gt;

&lt;p&gt;What do you think? Do you return the entity as the response from your POST and PUT endpoints? Always? Sometimes?&lt;/p&gt;

</description>
      <category>rest</category>
      <category>api</category>
      <category>performance</category>
    </item>
    <item>
      <title>OpenAPI examples are like comments</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Mon, 16 Jun 2025 11:18:45 +0000</pubDate>
      <link>https://dev.to/marianvarga/openapi-examples-are-like-comments-21lh</link>
      <guid>https://dev.to/marianvarga/openapi-examples-are-like-comments-21lh</guid>
      <description>&lt;p&gt;For projects involving a lot of integration I recommend to use the specification-first approach, starting with an OpenAPI document and generating implementation code stubs (e.g. in Java) from it because in my opinion it brings a better separation of the implementation from the specification and helps in the API maintenance. (See my &lt;a href="https://dastalvi.com/blog/what-is-your-favourite-standard/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;However, there are certainly things that can be improved in the OpenAPI specification itself and even more in the supporting tools. One such thing is the support for examples embedded in an OpenAPI specification. They are not mandatory, but including them will help both humans and automated test tools in understanding the data expected on the API.&lt;/p&gt;

&lt;p&gt;Unfortunately, the &lt;a href="https://spec.openapis.org/oas/v3.0.3#fixed-fields-11" rel="noopener noreferrer"&gt;OpenAPI specification&lt;/a&gt; says that the examples "should" match the schema, so it is not required for them to be valid.  This means they are like comments in any language, whose syntax is not checked. &lt;/p&gt;

&lt;p&gt;This is unfortunate because if someone changes a schema, it is easy for them to forget to make the respective changes in the examples. Outdated examples can be misleading. To prevent the situation, you can use &lt;a href="https://github.com/codekie/openapi-examples-validator" rel="noopener noreferrer"&gt;a tool that validates the examples&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>rest</category>
    </item>
    <item>
      <title>Hexagonal+optimistic may not be easy</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Tue, 25 Mar 2025 17:10:51 +0000</pubDate>
      <link>https://dev.to/marianvarga/hexagonaloptimistic-may-not-be-easy-3g50</link>
      <guid>https://dev.to/marianvarga/hexagonaloptimistic-may-not-be-easy-3g50</guid>
      <description>&lt;p&gt;Hexagonal or ports &amp;amp; adapters architecture is commonly used to structure the services so that details of the communication (including APIs and database access) do not leak into the business logic code.&lt;/p&gt;

&lt;p&gt;One problem with hexagonal architecture is that you need to map your data a lot between objects used in your API and persistence (ports) and your business logic (domain). Of course, tools like MapStruct for Java help in this.&lt;/p&gt;

&lt;p&gt;With the mappers in place, a pattern commonly emerges where at the beginning of an operation a persisted entity is read from a database, it is mapped to a domain object, some updates are made in the domain module and the updated domain object, after being mapped back to the persistence entity, is saved to the database.&lt;/p&gt;

&lt;p&gt;This pattern has a problem that it does not handle concurrent updates out of the box. If the database record is changed by someone else between your database read and write, your write blindly overwrites the record with old data, leading to data loss and/or corruption.&lt;/p&gt;

&lt;p&gt;Transactions might help, but you would need to remember to start the transaction and ask for an exclusive lock (SELECT FOR UPDATE in SQL) at the time you read the data. And you have to make sure the span of your transaction is until you save the modified data back.&lt;/p&gt;

&lt;p&gt;But transactions and exclusive locks create an overhead that can slow down your system.&lt;/p&gt;

&lt;p&gt;One option is to refactor your persistence code so that it provides methods that do atomic updates of exactly that part of your document that needs to be updated by the business operation. For that, you might need to stop using the repository pattern and go straight to e.g. MongoTemplate to have access to those atomic operations. And of course it has a big impact on the overall design of your application (service), you cannot do "read full object-map-modify-map-save full object" anymore.&lt;/p&gt;

&lt;p&gt;On the other hand, if you do not expect many concurrent updates, you can avoid that refactoring by introducing optimistic locking. Optimistic locking is no locking actually, it means adding a version number field to your persisted data and when writing back, checking if the version matches.&lt;/p&gt;

&lt;p&gt;Now the question is, how to preserve the version number when your database entity is mapped to the domain object and back? From my analysis and research it usually means that you need to relax the hexagonal architecture rules a little bit. For example, you can decide to put the version field in the business object even though it feels like it is specific to the persistence adapter.&lt;/p&gt;

&lt;p&gt;Here are some interesting links&lt;br&gt;
&lt;a href="https://softwareengineering.stackexchange.com/questions/369806/hexagonal-architecture-and-database-concurrency" rel="noopener noreferrer"&gt;Hexagonal Architecture and database concurrency (StackExchange)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://amanagrawal.blog/2019/08/27/building-an-event-driven-architecture-lessons-learned/" rel="noopener noreferrer"&gt;Building an Event Driven Architecture – Lessons Learned&lt;/a&gt; (see the comments below the article discussing the optimistic locking)&lt;/p&gt;

</description>
      <category>api</category>
      <category>java</category>
      <category>database</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>What is a URL?</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Tue, 25 Feb 2025 12:02:30 +0000</pubDate>
      <link>https://dev.to/marianvarga/what-is-a-url-4jfn</link>
      <guid>https://dev.to/marianvarga/what-is-a-url-4jfn</guid>
      <description>&lt;p&gt;We type it multiple times every day, we call it "a link" or "a web address", yet how many of us actually remember why it is called a URL? And why is it important for a good REST API?&lt;/p&gt;

&lt;p&gt;URLs (Uniform Resource Locators) are a subset of URIs (Uniform Resource Identifiers). There is another subset of URIs, the URNs (Uniform Resource Names). All URIs identify resources that are the basic building stones of REST.&lt;/p&gt;

&lt;p&gt;A URL (unlike a URN), besides being a unique identifier, contains information about how to locate and access the resource. The URL &lt;a href="https://dastalvi.com/contact" rel="noopener noreferrer"&gt;https://dastalvi.com/contact&lt;/a&gt; tells a client to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use the HTTP and TLS protocols (https),&lt;/li&gt;
&lt;li&gt;connect to the server with the domain name dastalvi.com&lt;/li&gt;
&lt;li&gt;and pass the path /contact to the server; the path is used to locate the resource within the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When creating something new, including an API, we often start by copying an example of something we know works. The &lt;a href="https://petstore.swagger.io/" rel="noopener noreferrer"&gt;Petstore sample API from Swagger&lt;/a&gt; is a famous example that we might want to use as a starting point, especially if we want to create an OpenAPI specification for our API.&lt;/p&gt;

&lt;p&gt;However, not every part of even a well-known example is good to copy without thinking if it fits into our context. Let's take a closer look at &lt;a href="https://petstore.swagger.io/#/pet/updatePet" rel="noopener noreferrer"&gt;the operation to update a pet using the PUT method&lt;/a&gt;. Can you see something dangerous?&lt;/p&gt;

&lt;p&gt;Clearly, the specification reuses the same Pet schema containing all fields of a pet including "id" for PUT /pet, POST /pet and GET /pet/{petId}&lt;/p&gt;

&lt;p&gt;Reuse is a good thing as it eliminates duplicates, right? But not all duplicates are good to be removed. Trying to be too clever and use the "id" from the PUT payload to identify the pet to be updated and removing it from the URL takes away an important feature of our URL: that it should be a URI, a unique identifier of the resource!&lt;/p&gt;

&lt;p&gt;The PUT method should be idempotent and it should replace the whole resource (identified by the URI) by the content provided in the request. The resource it modifies is the one we can retrieve by GET /pet/{petId}, where we clearly see that the "id" is included in the URL (because the GET method does not have a request body).&lt;/p&gt;

&lt;p&gt;Probably a more realistic approach is described in &lt;a href="https://github.com/microsoft/api-guidelines/blob/master/Guidelines.md" rel="noopener noreferrer"&gt;the API guidelines from Microsoft&lt;/a&gt;. It admits that there is usually a collection of entities (pets in our case) we work with.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#95-changing-collections" rel="noopener noreferrer"&gt;"Changing collections" section&lt;/a&gt; shows us that the PUT method should indicate which collection item we want to update.&lt;/p&gt;

&lt;p&gt;On the other hand, if we want to create a new item of the collection and let the server generate the new "id", we should use the POST method. Because sending multiple such requests will create multiple IDs, so this is exactly the situation where we need the non-idempotent POST.&lt;/p&gt;

</description>
      <category>api</category>
      <category>java</category>
      <category>rest</category>
    </item>
    <item>
      <title>What can happen if you skip the DTOs</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Mon, 29 Jul 2024 17:23:18 +0000</pubDate>
      <link>https://dev.to/marianvarga/what-can-happen-if-you-skip-the-dtos-aaj</link>
      <guid>https://dev.to/marianvarga/what-can-happen-if-you-skip-the-dtos-aaj</guid>
      <description>&lt;p&gt;It is nice that a framework like SpringBoot can do so many things for you.&lt;/p&gt;

&lt;p&gt;You just need a JPA entity class plus a simple repository interface and SpringData gives you all you need for typical CRUD database operations.&lt;/p&gt;

&lt;p&gt;You write a simple REST controller class and you have a REST API running, right?&lt;/p&gt;

&lt;p&gt;Hey, but you forgot to write a DTO! But why do you actually need it when your app could work without it?&lt;/p&gt;

&lt;p&gt;There are certainly some general reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;layered structure (e.g. hexagonal architecture or ports and adapters): for maintainability it is a good idea to decouple the external communication code form the core (business logic)&lt;/li&gt;
&lt;li&gt;security and performance: if you expose the database structure in your API as-is, you will soon get to a point where you expose more than needed; that can be misused by malicious actors or waste resources (CPU, memory and network bandwidth)&lt;/li&gt;
&lt;li&gt;DTOs, unlike JPA entities, can be immutable (you can use Java records) and that is good for data-driven (functional) programming style, nice unit tests, safer concurrency etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But other strange things can happen, too. I will show you one weird example based on my experience.&lt;/p&gt;

&lt;p&gt;This &lt;a href="https://github.com/marian-varga/api-without-dto/" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; contains a simple application that works without the DTOs. There is a User entity, each User can have multiple Transactions. We even have a Service bean between the repository and the RestController, catching possible database access exceptions.&lt;/p&gt;

&lt;p&gt;As we want to make a production-ready application, we do not want Hibernate to generate the DDL. Instead, we have a schema.sql that creates the tables (later we may switch to Flyway or Liquibase). For our simple example, we also have a data.sql so that our tables are not empty.&lt;/p&gt;

&lt;p&gt;When we run the application and call the API endpoint at &lt;a href="http://localhost:8080/users" rel="noopener noreferrer"&gt;http://localhost:8080/users&lt;/a&gt;, we get the expected JSON containing the users and their transactions.&lt;/p&gt;

&lt;p&gt;Now let's pay attention to the two lines of code in the Transaction class, marked //!!&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@JsonIgnore //!!&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;The first smell is that in the Transaction class we had to add the @JsonIgnore annotation to the User reference. Without that annotation the JSON serialization crashes due to infinite recursion.&lt;/p&gt;

&lt;p&gt;Now let's imagine that someone makes a mistake by adding another field (description) to the Transaction entity, but forgets to adjust the SQL statements (or runs the application against an environment where the schema change has not been applied).&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;private String description;//!!&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Of course, now the API call fails. But look at the error handling! The catch clause inside the UserService does not work as expected. Instead we can see a strange stack trace in the log:&lt;br&gt;
GlobalExceptionHandler : Unexpected error org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON:&lt;/p&gt;

&lt;p&gt;I once saw this situation (clearly, with an application much larger than this example) and it took me quite a while to understand why the SQL exception escaped the service and why I was getting an HttpMessageNotWritableException. Can you see it?&lt;/p&gt;

&lt;p&gt;What happens is, the UserService class (via the UserRepository) only queries the USERS database table. The Transaction entities are not part of the result because of the default Hibernate lazy loading. Only when the Jackson deserializer tries to create JSON from the User instance, it invokes its getTransactions method that makes Hibernate fetch the Transaction entities.&lt;/p&gt;

&lt;p&gt;This is why we get a strange stacktrace combining JSON and SQL stuff. The exception is caught by the GlobalExceptionHandler that does not know what to do with it, this is why the log message is "Unexpected error".&lt;/p&gt;

&lt;p&gt;I hope this little exercise will make you understand more deeply how dangerous it is to allow different layers of your application to mix. Seeing just the "sunny day" scenarios of your application while it is still small may lead some developers to continue doing the wrong thing until it is too late.&lt;/p&gt;

&lt;p&gt;You do not have to write the boilerplate code mapping the fields between your DTO and the other layers of your application. &lt;a href="https://mapstruct.org/" rel="noopener noreferrer"&gt;MapStruct&lt;/a&gt; can do it for you.&lt;/p&gt;

</description>
      <category>api</category>
      <category>springboot</category>
      <category>java</category>
      <category>rest</category>
    </item>
    <item>
      <title>AsyncAPI: a practical look</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Fri, 03 May 2024 10:47:39 +0000</pubDate>
      <link>https://dev.to/marianvarga/asyncapi-a-practical-look-2hln</link>
      <guid>https://dev.to/marianvarga/asyncapi-a-practical-look-2hln</guid>
      <description>&lt;p&gt;On the occasion of the release of the new major version of the AsyncAPI I wrote a &lt;a href="https://dastalvi.com/blog/platform-independent-async-integrations/" rel="noopener noreferrer"&gt;rather theoretical post&lt;/a&gt; encouraging the use of AsyncAPI as a platform-independent standard.&lt;/p&gt;

&lt;p&gt;But a standard is only useful if it can help you in real-life development scenarios. Just a simple Google search reveals that the older sibling, OpenAPI, has a much larger and more mature community. In the case of a machine-readable API specification language the tooling support is crucial. I will describe my experience with the tools so far.&lt;/p&gt;

&lt;h1&gt;
  
  
  AsyncAPI Studio
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://studio.asyncapi.com/" rel="noopener noreferrer"&gt;studio.asyncapi.com&lt;/a&gt; is a nice online visualisation tool to check if your AsyncAPI specification is valid. The page says it is "BETA", but it looked quite reliable when I tried it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Code generators: the devil hidden in the schema details
&lt;/h1&gt;

&lt;p&gt;In asynchronous APIs, unlike the REST (HTTP) APIs, we usually do not rely so much on common features of the protocol used. Instead of things like HTTP verbs, status codes and standard headers we concentrate on the payload of the messages. So it is nice that there are code generators that can produce a ready-to-run Spring Boot or Spring Cloud Stream application, but the most important part is the code that serializes/deserializes the payloads. Topic names and broker specifics are usually placed in separate configuration files anyway.&lt;/p&gt;

&lt;h1&gt;
  
  
  Different JSON schema languages
&lt;/h1&gt;

&lt;p&gt;At first glance the schemas part of AsyncAPI looks the same as in OpenAPI. But the specifications are not exactly the same. AsyncAPI even in its latest version 3.0 only requires its users (including tools) to support a schema based on &lt;a href="https://json-schema.org/specification-links#draft-7" rel="noopener noreferrer"&gt;JSON schema draft 7&lt;/a&gt; (from 2018) plus features specific to Async API, see the "Schema formats table" section of &lt;a href="https://www.asyncapi.com/docs/reference/specification/v3.0.0" rel="noopener noreferrer"&gt;the specification&lt;/a&gt;. There are two more recent versions of the JSON schema, the latest is called &lt;a href="https://json-schema.org/specification-links#2020-12" rel="noopener noreferrer"&gt;2020-12&lt;/a&gt;. And OpenAPI has its own set of extensions over the JSON schema.&lt;/p&gt;

&lt;h1&gt;
  
  
  No luck with polymorphism
&lt;/h1&gt;

&lt;p&gt;I will illustrate the limitations of the generators on an example schema using polymorphic types with a discriminator field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;asyncapi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.6.0&lt;/span&gt;
&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Example of schema using polymorphism&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.6.0&lt;/span&gt;
&lt;span class="na"&gt;channels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;pet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet"&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schemas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Pet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
      &lt;span class="na"&gt;discriminator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;petType&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
        &lt;span class="na"&gt;petType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;petType&lt;/span&gt;
    &lt;span class="c1"&gt;## applies to instances with `petType: "Cat"`&lt;/span&gt;
    &lt;span class="c1"&gt;## because that is the schema name&lt;/span&gt;
    &lt;span class="na"&gt;Cat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A representation of a cat&lt;/span&gt;
      &lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;huntingSkill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
              &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The measured skill for hunting&lt;/span&gt;
              &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clueless&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lazy&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;adventurous&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aggressive&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;huntingSkill&lt;/span&gt;
    &lt;span class="c1"&gt;## applies to instances with `petType: "Dog"`&lt;/span&gt;
    &lt;span class="c1"&gt;## because that is the schema name&lt;/span&gt;
    &lt;span class="na"&gt;Dog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A representation of a dog&lt;/span&gt;
      &lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;packSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;integer&lt;/span&gt;
              &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int32&lt;/span&gt;
              &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;the size of the pack the dog is from&lt;/span&gt;
              &lt;span class="na"&gt;minimum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;packSize&lt;/span&gt;
    &lt;span class="c1"&gt;## applies to instances with `petType: "StickBug"`&lt;/span&gt;
    &lt;span class="c1"&gt;## because that is the required value of the discriminator field,&lt;/span&gt;
    &lt;span class="c1"&gt;## overriding the schema name&lt;/span&gt;
    &lt;span class="na"&gt;StickInsect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;A representation of an Australian walking stick&lt;/span&gt;
      &lt;span class="na"&gt;allOf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;$ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#/components/schemas/Pet'&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;object&lt;/span&gt;
          &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;petType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;const&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StickBug&lt;/span&gt;
            &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;color&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is a valid AsyncAPI specification (you can check it in the AsyncAPI Studio), however none of the currently available AsyncAPI code generators can generate a usable (Java) code from it. By usable I mean a code that uses the discriminator field value to choose what Java class to instantiate and then deserializes the JSON object in the instance correctly.&lt;/p&gt;

&lt;p&gt;The "most official" &lt;a href="https://github.com/asyncapi/generator" rel="noopener noreferrer"&gt;AsyncAPI generator&lt;/a&gt; linked from the AsyncAPI site will follow the references in the "allOf" structure to generate the subclasses of the Pet class. But it will ignore the "discriminator" logic and actually fail because it does know what to do with the "petType" attribute repeated in the derived types (e.g. StickInsect).&lt;/p&gt;

&lt;p&gt;A less important thing I do not like about the "official" generator is that it is implemented in JavaScript (it uses the schema generator library Modelina written in TypeScript). For a Node.js backend this may be nice, but for Java (JVM) based backends it is not ideal to have to invoke npm as part of the build process. Especially knowing that the OpenAPI generator that is much more mature and probably used for the synchronous (REST) APIs of the same backend service, is implemented in Java and runs in Maven/Gradle nicely.&lt;/p&gt;

&lt;p&gt;If you convert the above schema to the OpenAPI format and run the OpenAPI generator on it, you get the polymorphic code that you expect.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://www.asyncapi.com/tools" rel="noopener noreferrer"&gt;Tools dashboard&lt;/a&gt; of the OpenAPI site you can find two more generators: MultiAPI generator and ZenWave SDK, both written in Java. Unfortunately these are even less mature than the JavaScript one: They don't even detect there are types derived from the base Pet type for which classes should be generated.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I still think it is a good idea to start using AsycAPI for asynchronous APIs. But beware of possible problems if you want to use complex schemas. The safest is to use just simple schemas and wait for the tooling to catch up. You always have to verify your schemas with the actual generator you want to use. Even JSON schema draft 7 contains concepts similar to the discriminator approach above (if-then and oneOf), but neither of them made the generators give me what I needed.&lt;/p&gt;

&lt;p&gt;Actually, the AsyncAPI 3.0 permits to use the OpenAPI schemas in AsyncAPI using what they call a &lt;a href="https://www.asyncapi.com/docs/reference/specification/v3.0.0#multiFormatSchemaObject" rel="noopener noreferrer"&gt;"Multi Format Schema Object"&lt;/a&gt;. Although the specification does not require, only recommends to support the OpenAPI schema, I would really love to see tools supporting it so that we can share the same schema format for AsyncAPI and OpenAPI.&lt;/p&gt;

</description>
      <category>integration</category>
      <category>kafka</category>
      <category>asyncapi</category>
      <category>eventdriven</category>
    </item>
    <item>
      <title>Why I don't like "v1" in an API URL</title>
      <dc:creator>marian-varga</dc:creator>
      <pubDate>Thu, 21 Mar 2024 08:50:02 +0000</pubDate>
      <link>https://dev.to/marianvarga/why-i-dont-like-v1-in-an-api-url-407f</link>
      <guid>https://dev.to/marianvarga/why-i-dont-like-v1-in-an-api-url-407f</guid>
      <description>&lt;p&gt;I've recently read a &lt;a href="https://mezocode.com/best-practices-for-api-design-in-java/" rel="noopener noreferrer"&gt;quite useful article on API design best practices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But I wasn't quite comfortable with one point in it, the one advocating putting a "v1" in the API URL paths to support versioning. After thinking about it a little I have decided to explain my reasons in more detail.&lt;/p&gt;

&lt;p&gt;TL;DR &lt;a href="https://codibly.com/news-insights/yagni-how-to-do-things-when-you-actually-need-them-to-be-done/#:~:text=YAGNI%20is%20an%20acronym%20for,shouldn't%20be%20done%20NOW." rel="noopener noreferrer"&gt;YAGNI&lt;/a&gt; &amp;amp; &lt;a href="https://en.wikipedia.org/wiki/KISS_principle" rel="noopener noreferrer"&gt;KISS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Similarly to code expressed in more lines of code, adding more abstraction layers prematurely should be considered a liability, not an asset. Once we decide on the convention to put the version number in the URL, in order to be consistent, we have to incur the cost of maintaining the convention for all our API endpoints.&lt;/p&gt;

&lt;p&gt;Moreover, having the version number can support a false feeling that making incompatible API changes is actually cheap. It is not. You have to think about all the clients of your API and support (including regression testing etc.) multiple versions of your API until you are sure a certain version is not used by anyone.&lt;/p&gt;

&lt;p&gt;Another important thing to consider is that in practice it is quite hard to draw the line when exactly an API becomes backwards incompatible, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Although it is not nice or recommended, it can happen that there are clients not ignoring newly added response attributes.&lt;/li&gt;
&lt;li&gt;Is changing caching policy, returning a response using Transfer-Encoding: chunked instead of Content-Length etc. a compatible API change?&lt;/li&gt;
&lt;li&gt;You just fix a bug in your API, but there may be clients who work around the bug and actually rely on the buggy behaviour!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the clients of your API can tell for sure if a change is really compatible or not.&lt;/p&gt;

&lt;p&gt;In many cases you decide that even though in theory your API change may not be backwards-compatible, you just expect the clients to adjust. A typical scenario is when you have control over the development of the client(s), like front-end developers connecting to a back-end developed within the same agile team. Incrementing a version number in the URL in such a case "just because it is what the book says" would be just an unnecessary additional work and a cause of fights.&lt;/p&gt;

&lt;p&gt;A major API change quite often brings a change of semantics of your endpoints, so maybe just renaming them to their accurate business meaning is enough and you do not need "v1" and "v2".&lt;/p&gt;

&lt;p&gt;If you really need to support multiple versions, you probably just need it for one or a few endpoints.&lt;/p&gt;

&lt;p&gt;Now if your "v1" is at the root of the path, you&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;either have to change the path of all endpoints when you just needed to change a few of them&lt;/li&gt;
&lt;li&gt;or you break the path hierarchy because some endpoints will be under "v1", while others under "v2"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If "v1" is at the end of the path then you cannot deal with it as with a common prefix in just one place, you have to maintain it in all the endpoints. Doubts and inconsistencies can pop up very quickly: Is it /customers/v1/{id} or /customers/{id}/v1 ?&lt;/p&gt;

&lt;p&gt;So, I think it is better to skip "v1" and wait until you really need a "v2". And when it happens, you should consider, instead of putting the "v2" in the URL path, to use an HTTP request header (either a custom one or maybe as a suffix of "Content-type").&lt;/p&gt;

</description>
      <category>api</category>
      <category>rest</category>
      <category>integration</category>
    </item>
  </channel>
</rss>
