<?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: Julien Lengrand-Lambert</title>
    <description>The latest articles on DEV Community by Julien Lengrand-Lambert (@jlengrand).</description>
    <link>https://dev.to/jlengrand</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%2F95838%2F00680dfa-f143-4836-aa4f-77ec8185866b.jpeg</url>
      <title>DEV Community: Julien Lengrand-Lambert</title>
      <link>https://dev.to/jlengrand</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jlengrand"/>
    <language>en</language>
    <item>
      <title>KotlinConf 2025 is a real bowl of fresh air for backend Devs</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Thu, 22 May 2025 14:34:02 +0000</pubDate>
      <link>https://dev.to/jlengrand/kotlinconf-2025-is-a-real-bowl-of-fresh-air-for-backend-devs-31ng</link>
      <guid>https://dev.to/jlengrand/kotlinconf-2025-is-a-real-bowl-of-fresh-air-for-backend-devs-31ng</guid>
      <description>&lt;p&gt;&lt;em&gt;TL;DR : After years of focus on the compiler and KMP, The JetBrains folks are coming it out with tons of announcements for server folks too, and that feels great&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The past few years
&lt;/h2&gt;

&lt;p&gt;You probably know me by now, I've been liking Kotlin a lot for a long time. Back in 2019 already we were the first at ING &lt;a href="https://medium.com/ing-blog/introducing-kotlin-at-ing-a-long-but-rewarding-story-1bfcd3dc8da0?ref=lengrand.fr" rel="noopener noreferrer"&gt;moving some of of our production code from Java to Kotlin&lt;/a&gt;. Over the past years though, &lt;a href="https://www.reddit.com/r/Kotlin/comments/1gq7zwg/comment/lww1spb/?context=3&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;I've also been vocal online&lt;/a&gt; about my worries that all of the love was going towards KMP. And Google deciding to &lt;a href="https://bsky.app/profile/jlengrand.bsky.social/post/3llqnajudqf2u?ref=lengrand.fr" rel="noopener noreferrer"&gt;sunset the (non Android) Kotlin category&lt;/a&gt; has just been another pointer in the same direction. I've even mentioned this exact thing already right after visiting KotlinConf 2023.&lt;/p&gt;

&lt;p&gt;[&lt;/p&gt;

&lt;p&gt;My KotlinConf experience&lt;/p&gt;

&lt;p&gt;My experience and impressions attending KotlinConf a couple weeks back. And some thoughts on the future of Kotlin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16m3ssz2rzbrlwo5eyi4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16m3ssz2rzbrlwo5eyi4.png" width="256" height="256"&gt;&lt;/a&gt;Julien's DevRel cornerJulien&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5re5sqpao72d7klrtyc8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5re5sqpao72d7klrtyc8.jpeg" width="800" height="637"&gt;&lt;/a&gt;&lt;br&gt;
](&lt;a href="https://dev.to/jlengrand/my-kotlinconf-experience-45mf"&gt;https://dev.to/jlengrand/my-kotlinconf-experience-45mf&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Even with Java releasing more often, and getting more and more new features every iteration I'm still much happier using Kotlin. But for pure backend developers is the developer experience still that differentiating that it makes it worth switching? After all, Java got pretty good records those days. A much better functional programming experience. And virtual threads. The list goes on....&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note : This article is my personal impression based on my usage of the language. Depending what you're working on, your opinion may vary 😊. I've also included many screenshots from the Keynote itself,&lt;/em&gt; &lt;a href="https://www.youtube.com/watch?v=PYAPymKRKVA&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;&lt;em&gt;please watch it&lt;/em&gt;&lt;/a&gt; &lt;em&gt;for the complete announcements!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note 2 : There's also been lots of great AI related announcements, but I'll intentionally skip them in this article. I'm generally happy JetBrains picks up the AI wave and innovates while staying open.&lt;/em&gt; &lt;a href="https://dev.to/jlengrand/my-experience-using-junie-for-the-past-few-months-4f1b-temp-slug-8593710"&gt;&lt;em&gt;I'm a mostly happy user&lt;/em&gt;&lt;/a&gt; &lt;em&gt;of their product myself.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New language features
&lt;/h2&gt;

&lt;p&gt;As a backend engineer, I think the main improvements I've personally seen in Kotlin that made a difference to me the past years are &lt;a href="https://dev.to/jlengrand/measuring-time-and-durations-in-kotlin-4812"&gt;the time API&lt;/a&gt; (back in 2021!) and the K2 compiler. Of course, the K2 compiler is a huge (and needed) improvement that came with Kotlin 2.0. But it also didn't bring a huge amount of new language features.&lt;/p&gt;

&lt;p&gt;Now, &lt;strong&gt;today's keynote was a VERY welcome change to this&lt;/strong&gt;. Others will do a complete breakdown better than me but here some actual new language features I'm excited about!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name-based destructuring to ensure I am grabbing the right properties. An actual improvements compared to the current version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnonvg95epkqyj1c8xg2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnonvg95epkqyj1c8xg2o.png" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot from Day 1 Keynote: &lt;a href="https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb" rel="noopener noreferrer"&gt;https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich errors : an improvement to error handling, sitting on top of the already great null safety features of the language. Not only do we now have complete return types but they also get propagated so we can neatly handle all error cases in a single location.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooaqk2k4yypmeqwhvygj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fooaqk2k4yypmeqwhvygj.png" width="800" height="275"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot from Day 1 Keynote: &lt;a href="https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb" rel="noopener noreferrer"&gt;https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Even Kotlin compiler plugins are getting more powerful, with Power Assert offering an expressive and clear error message. Look at this, we're almost topping the already amazing &lt;a href="https://guide.elm-lang.org/error_handling/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Elm error handling&lt;/a&gt; capabilities. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3acdhum1rte9q7nw2z46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3acdhum1rte9q7nw2z46.png" width="800" height="327"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot from Day 1 Keynote: &lt;a href="https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb" rel="noopener noreferrer"&gt;https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is developer experience like I haven't seen in a long time from the teams at JetBrains, and it genuinely makes me happy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling ecosystem, and strategic partnerships
&lt;/h2&gt;

&lt;p&gt;I've already mentioned lately that I was a big fan of &lt;a href="https://kotlinlang.org/docs/kotlin-notebook-overview.html?ref=lengrand.fr" rel="noopener noreferrer"&gt;Kotlin notebooks&lt;/a&gt;. I find them generally amazing, because they combine the unique experience of Python Notebooks with the gigantic JVM ecosystem (and without the Python horrible dependency management issues).&lt;/p&gt;

&lt;p&gt;[&lt;/p&gt;

&lt;p&gt;Julien Lengrand-Lambert 🥑👋 (&lt;a class="mentioned-user" href="https://dev.to/jlengrand"&gt;@jlengrand&lt;/a&gt;.bsky.social)&lt;/p&gt;

&lt;p&gt;Been doing some data analysis lately and #Kotlin notebooks, together with dataframe, kandy and sqlite are a really freaking powerful combination! Can’t wait to show you the results :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3j97fk3pzuhja61hb8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3j97fk3pzuhja61hb8y.png" width="180" height="180"&gt;&lt;/a&gt;Bluesky Social&lt;/p&gt;

&lt;p&gt;](&lt;a href="https://bsky.app/profile/jlengrand.bsky.social/post/3lgg7aa5yxe2l?ref=lengrand.fr" rel="noopener noreferrer"&gt;https://bsky.app/profile/jlengrand.bsky.social/post/3lgg7aa5yxe2l?ref=lengrand.fr&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I was legit 🤯 this morning when I saw that the folks at JetBrains combine this already amazing experience together with the ecosystem that pretty much every JVM dev uses on the planet : Spring!&lt;/p&gt;

&lt;p&gt;The screenshots aren't doing justice to the announcement, so please just go and &lt;a href="https://www.youtube.com/watch?v=PYAPymKRKVA&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;have a look at the Keynote&lt;/a&gt; yourself. The latest version of notebooks can attach itself to a running Spring kernel and have access to all of its context. And that even for applications that aren't using Kotlin yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7utckwswn8buvr4lu5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7utckwswn8buvr4lu5m.png" width="800" height="603"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Kotlin notebook, straight from a running Spring kernel&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Again, this is next level for me in terms of developer experience : combining the flexibility of notebooks for experimentation together with the sturdiness of a production like application. With the kandy and dataframe data manipulation capabilities, I can definitely see this speed up my day to day development speed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjhryez5cfe4p6r0ztyg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdjhryez5cfe4p6r0ztyg.png" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And of course, JetBrains also announcing a strategic partnership with Spring on stage shows that they clearly intend to give us "corporate" backend developers some serious love.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vb4gh6t8gk9pvku6735.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7vb4gh6t8gk9pvku6735.png" width="800" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Growing Kotlin Foundation
&lt;/h2&gt;

&lt;p&gt;For a long time, Kotlin was completely in the hands of JetBrains. This has massively changed over the past years with the creation of the Kotlin Foundation.&lt;/p&gt;

&lt;p&gt;As a professional having to pick a technology, it is crucial to look at how promising its future looks. Seeing that Kotlin is jointly supported by several companies, and especially companies that aren't making a living of the language makes it a much safer business case to adopt inside a company.&lt;/p&gt;

&lt;p&gt;Seeing two more companies (and none other than Meta) join the foundation gives a great idea of how bright of a future Kotlin has 😊.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlwg7ryxol2oxm313e8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnlwg7ryxol2oxm313e8u.png" width="800" height="307"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Kotlin foundation members&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kotlin built Java library ecosystem
&lt;/h2&gt;

&lt;p&gt;A few weeks back, I was mentioning that for the first time I found a library that was completely built in Kotlin, but had a clear Java compatibility too. If you're interested, I'm talking about the &lt;a href="https://github.com/uakihir0/kbsky?ref=lengrand.fr" rel="noopener noreferrer"&gt;KBsky&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;[&lt;/p&gt;

&lt;p&gt;Julien Lengrand-Lambert 🥑👋 (&lt;a class="mentioned-user" href="https://dev.to/jlengrand"&gt;@jlengrand&lt;/a&gt;.bsky.social)&lt;/p&gt;

&lt;p&gt;Interestingly, this is the first time ever that I find a more mature ecosystem of libraries for my need in #kotlin than in #java quite interesting isn’t it. I was looking for ATProto libraries, it seems the main java was got deprecated in favor of a kotlin version? &lt;a href="https://github.com/uakihir0/bsky4j" rel="noopener noreferrer"&gt;https://github.com/uakihir0/bsky4j&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3j97fk3pzuhja61hb8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3j97fk3pzuhja61hb8y.png" width="180" height="180"&gt;&lt;/a&gt;Bluesky Social&lt;/p&gt;

&lt;p&gt;](&lt;a href="https://bsky.app/profile/jlengrand.bsky.social/post/3lmk4xy6s2o2q?ref=lengrand.fr" rel="noopener noreferrer"&gt;https://bsky.app/profile/jlengrand.bsky.social/post/3lmk4xy6s2o2q?ref=lengrand.fr&lt;/a&gt;)&lt;br&gt;
&lt;em&gt;A Java library, built in Kotlin&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today, I learnt that the two most successful JVM AI libraries (the OpenAI Java SDK and the Anthropic Java SDK) both are actually written in Kotlin.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficvorwzy81dz8x2xpojf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficvorwzy81dz8x2xpojf.png" width="800" height="295"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;See Keynote for complete info : &lt;a href="https://www.youtube.com/watch?v=PYAPymKRKVA" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=PYAPymKRKVA&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8weuum2e2bpbih7y8b4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8weuum2e2bpbih7y8b4.png" width="800" height="313"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;See Keynote for complete info : &lt;a href="https://www.youtube.com/watch?v=PYAPymKRKVA" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=PYAPymKRKVA&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I find this a great sign for the language. Of course, the amount of Java developers out there is much larger than people writing Kotlin and it makes sense for library builders to want to maximize their reach. But them choosing Kotlin to write the library speaks volume about the quality of the language, but is also a great sign for its future.&lt;/p&gt;

&lt;h2&gt;
  
  
  The official KOTLIN LANGUAGE SERVER
&lt;/h2&gt;

&lt;p&gt;This is, &lt;a href="https://bsky.app/profile/jlengrand.bsky.social/post/3lpqo5stye22h?ref=lengrand.fr" rel="noopener noreferrer"&gt;by far&lt;/a&gt;, the announcement I am most excited about and I genuinely didn't see it coming. JetBrains announced the official release of their Kotlin Language Server protocol today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b288gf1tge4jwxzto2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b288gf1tge4jwxzto2t.png" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For those unaware, the &lt;a href="https://microsoft.github.io/language-server-protocol/?ref=lengrand.fr" rel="noopener noreferrer"&gt;language server&lt;/a&gt; protocol "defines the protocol used between an editor or IDE and a language server that provides language features like auto complete, go to definition, find all references etc.". Having an official language server protocol implementation basically means that all IDEs (from vim to IntelliJ, or SublimeText) can support the language equally well.&lt;/p&gt;

&lt;p&gt;The fact that JetBrains is behind Kotlin always made it rather logical for them to not Open-Source a language server. After all, they're selling their IDEs and that'd pretty much mean they're competing against themselves. And it's always been one of my biggest issues with the language.&lt;/p&gt;

&lt;p&gt;[&lt;/p&gt;

&lt;p&gt;Julien Lengrand-Lambert 🥑👋 (&lt;a class="mentioned-user" href="https://dev.to/jlengrand"&gt;@jlengrand&lt;/a&gt;.bsky.social)&lt;/p&gt;

&lt;p&gt;Agreed. Absolutely love IntelliJ and the company behind it, but I don’t like have to start the powerhouse all the time for some simple scripting. Let me use IntelliJ for feature full, multiplatform stuff and give me a powerful language server for the simple backend / scripting thing please&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3j97fk3pzuhja61hb8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3j97fk3pzuhja61hb8y.png" width="180" height="180"&gt;&lt;/a&gt;Bluesky Social&lt;/p&gt;

&lt;p&gt;](&lt;a href="https://bsky.app/profile/jlengrand.bsky.social/post/3lfadcojxvk2a?ref=lengrand.fr" rel="noopener noreferrer"&gt;https://bsky.app/profile/jlengrand.bsky.social/post/3lfadcojxvk2a?ref=lengrand.fr&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Yes, IntelliJ is a great IDE. I use it every single day, have paid for my license for years and I love it. But in simply just don't believe in the success of gated products. More and more people use VSCode every day. People now use Cursor. Or SublimeText. Some still use vim. Forcing folks to use a specific IDE just feels wrong and tames the excitement using a new language should bring.&lt;/p&gt;

&lt;p&gt;Yes, there's been &lt;a href="https://github.com/fwcd/kotlin-language-server?ref=lengrand.fr" rel="noopener noreferrer"&gt;community driven language server implementations&lt;/a&gt; for Kotlin, but they've always been struggling with support. And literally being a company building IDEs for a living, JetBrains always felt like the best party to handle this properly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1x5b3yoa54t79yrbong.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1x5b3yoa54t79yrbong.png" width="800" height="151"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The main kotlin language server implementation struggling with support&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I'm over the moon that JetBrains has the courage to open up and even lead the language server efforts in the future. It shows a strong trust in the future of the language and that can only help drive adoption across the industry.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ING as use case
&lt;/h2&gt;

&lt;p&gt;As I mentioned already, I was part of the first team to start using Kotlin at ING back in 2019. Five years later, adoption has grown a lot internally and I was super happy to see JetBrains offer us to be one of the leading industry use cases for their server side Keynote.&lt;/p&gt;

&lt;p&gt;Kotlin powers some of our critical services today, and it's a great symbol of how good and powerful the language is. I can't wait to see where we'll be in 5 years.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqiaj4t3p1sa0oh2qqsiu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqiaj4t3p1sa0oh2qqsiu.jpeg" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Simone, one of my colleagues, talking about Kotlin adoption at ING. Well done Simone!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've always been a fan of Kotlin, and it's been an honour to be able to contribute to the content of the program, no matter how small my contribution was.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5nt4rjti1orfdlrpgz6.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5nt4rjti1orfdlrpgz6.jpeg" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Recording the video, behind the scenes&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There's much more I'd like to talk about (amper, klibs, kmp, ...) but I'll keep it to the essential : It feels to me with their keynote that JetBrains intentionally started putting backend developers in the spotlight again. They made big, impactful announcements, both at an ecosystem and language levels and I can't wait to try out all of the new goodies.&lt;/p&gt;

&lt;p&gt;Well done JetBrains, well done!&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>jvm</category>
      <category>conference</category>
      <category>development</category>
    </item>
    <item>
      <title>Visualizing your AAARRP priorities as a way to manage up in your DevRel team</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Tue, 12 Dec 2023 09:46:10 +0000</pubDate>
      <link>https://dev.to/jlengrand/visualizing-your-aaarrp-priorities-as-a-way-to-manage-up-in-your-devrel-team-3ijp</link>
      <guid>https://dev.to/jlengrand/visualizing-your-aaarrp-priorities-as-a-way-to-manage-up-in-your-devrel-team-3ijp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1592111332908-f8f7fe1bb041%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDh8fHN0cmF0ZWd5fGVufDB8fHx8MTcwMjM3Mzc1NHww%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1592111332908-f8f7fe1bb041%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DM3wxMTc3M3wwfDF8c2VhcmNofDh8fHN0cmF0ZWd5fGVufDB8fHx8MTcwMjM3Mzc1NHww%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" width="2000" height="2000"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR : we have been using &lt;a href="https://www.leggetter.co.uk/aaarrrp/?ref=lengrand.fr" rel="noopener noreferrer"&gt;the AAARRRP framework&lt;/a&gt; as a visual and easy way to align with stakeholders which types of activities are the most relevant for our customers.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Some context
&lt;/h3&gt;

&lt;p&gt;Even though I have been doing Developer Relations for a few years unofficially or semi-officially, it was only two years ago that I got my first official role as a Developer Advocate. And after just a few months, I was offered to lead the team. We're a small team of three (me included) and our team falls under engineering.&lt;/p&gt;

&lt;p&gt;It was never really an issue for us to create a strategy on what to achieve, or how to achieve it. We focussed on our 3 pillars: &lt;a href="https://www.whatisdevrel.com/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Code, Content and Community&lt;/a&gt; and selected the most relevant activities.&lt;/p&gt;

&lt;p&gt;For example, being a B2B company we knew that running the conference circuit wasn't the most efficient way to create value, for the simple reason that people wouldn't be able to sign up for our services. We quickly decided instead to embed ourselves close to documentation and provide many samples for the companies who were using us, bringing a lot of internal feedback at the same time.&lt;/p&gt;

&lt;p&gt;But even though we had no doubt about our priorities, it wasn't always as clear for higher management, nor other stakeholders. After all, Developer Relations was a relatively new thing inside the company and only a few people had direct experience with the domain in the past. And we all know that even for companies seasoned with DevRel, measuring impact and making sure activities align are a difficult topic.&lt;/p&gt;

&lt;p&gt;We received a lot of challenging questions over time regarding our priorities :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We see that the &lt;em&gt;insert competitor&lt;/em&gt; DevRel team is very present on Youtube. How is it that your are not doing the same? &lt;/li&gt;
&lt;li&gt;How is it that your Twitter account gets very few likes compared to &lt;em&gt;insert other company&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Why do we not see many community contributions on your GitHub account, compared to &lt;em&gt;insert large company&lt;/em&gt;? &lt;/li&gt;
&lt;li&gt;Would you please join us at the booths for &lt;em&gt;insert conference,&lt;/em&gt; we want to increase hiring.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All those questions are very fair, and they deserve clear answers. Thing is, they were quite obvious for us given the context we had and it was a struggle for me to communicate those answers at scale and in a strategic way (understand : Find a way that people understand those answers, and hopefully give enough context that they come to the same decisions by themselves).&lt;/p&gt;

&lt;p&gt;It's after reading about the AAARRRP framework that a potential solution came to mind 😊.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick sideline about priorities / activities
&lt;/h3&gt;

&lt;p&gt;My point with this method is to focus on how to communicate in a scalable way &lt;strong&gt;the type of activity&lt;/strong&gt; we plan on focussing on, &lt;strong&gt;not the exact activity performed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, we might want people to understand that writing code samples is the most impactful activity we can do.&lt;/p&gt;

&lt;p&gt;However, we will not use it to communicate &lt;strong&gt;which&lt;/strong&gt; code sample to write. For this, we might instead use a combination of data sources like documentation statistics, or support tickets for example. I may write more about this later in another article.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quick intro about the AAARRRP framework
&lt;/h3&gt;

&lt;p&gt;I'm not going to expand myself much about this, because I would be ripping off the original article. I recommend you to &lt;a href="https://www.leggetter.co.uk/aaarrrp/?ref=lengrand.fr" rel="noopener noreferrer"&gt;go read it&lt;/a&gt; instead if you have never heard of it before.&lt;/p&gt;

&lt;p&gt;Here is the very minimal version. As Developer Relations team, we can operate on several key moments of the customer journey :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wqtwpqc6s5oqvom8dni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wqtwpqc6s5oqvom8dni.png" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" width="800" height="222"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The AAARRRP framework funnel&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Depending on the type of company we're working on and the strategic priorities of that company, some of those parts will be more crucial than others.&lt;/p&gt;

&lt;p&gt;Early on, many SAAS products want to focus on &lt;strong&gt;awareness&lt;/strong&gt; and &lt;strong&gt;acquisition&lt;/strong&gt; to drive as many new customers in as possible, and grow fast (The famous &lt;a href="https://www.swyx.io/measuring-devrel?ref=lengrand.fr" rel="noopener noreferrer"&gt;MAD&lt;/a&gt;) .&lt;/p&gt;

&lt;p&gt;As your product grows in maturity, you may want to increase the "stickiness" of your product and increase &lt;strong&gt;retention&lt;/strong&gt; , or work on &lt;strong&gt;activation&lt;/strong&gt; of additional features for your existing customer base.&lt;/p&gt;

&lt;p&gt;Typical DevRel activities that we carry have impact of different parts of that funnel (usually several at the same time).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3r4ubqmifzob9b1v0p9o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3r4ubqmifzob9b1v0p9o.png" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" width="800" height="557"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Activities and funnel mapping. Original from &lt;a href="https://www.leggetter.co.uk/aaarrrp/" rel="noopener noreferrer"&gt;https://www.leggetter.co.uk/aaarrrp/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: The original post also adds weight to the equation in order to select activities. We will not here, for simplicity's sake. The whole idea stays valid here&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  From AAARRRP to strategic communication
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Note: All those charts have been altered and do not contain internal information&lt;/em&gt; 😊&lt;/p&gt;

&lt;p&gt;So far, nothing new under the sun. That's where we'll go just one step further and use the content above for strategic communication.&lt;/p&gt;

&lt;p&gt;Let's say that based on the current context, &lt;strong&gt;we decide to focus on activation, retention and product&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can draw this as a chart to help communication with stakeholders. &lt;/li&gt;
&lt;li&gt;We can also draw (rough, given we do not have internal knowledge) equivalent charts for other companies in the space for comparison. &lt;/li&gt;
&lt;li&gt;We can even draw our own chart year on year, to show how our activities vary as priorities shift, or impact decreased (&lt;a href="https://en.wikipedia.org/wiki/Diminishing_returns?ref=lengrand.fr" rel="noopener noreferrer"&gt;law of diminishing returns&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Now for the key part :&lt;/strong&gt; We use these charts to validate assumptions with  stakeholders, &lt;strong&gt;who will be able to derive the corresponding activities themselves.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, given the decision above we can draw this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26qa7kv9psxc253xiq1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26qa7kv9psxc253xiq1n.png" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" width="800" height="494"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Possible DevRel priorities focussing on activation, retention and product&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With those priorities agreed, the common understanding is that we should focus on activities like Code Samples, Guides, Tutorials, and answering Stack Overflow questions.&lt;/p&gt;

&lt;p&gt;Again, we can more easily explain why we have a lesser focus on social media and the conference circuit that others by comparing main focuses between entities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bmc91mtu7allzyad2aa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bmc91mtu7allzyad2aa.png" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" width="800" height="494"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;possible DevRel team profile from another company focussing more on MAD&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once the shape of our strategy is agreed to, the type of activities expected become clearer for everyone and we can focus on the impact of those activities. And if the priorities change, it's always time to come back to the drawing board 🎉.&lt;/p&gt;

&lt;p&gt;Let's imagine now that our company's internal focus heavily shifts towards "closing deals" because we need to extend our runway. It might be time for the DevRel team to &lt;strong&gt;start acting on the Revenue&lt;/strong&gt; part of the funnel, reduce Product efforts &lt;strong&gt;and do Pre-Sales activities&lt;/strong&gt;. We can update the plan, propose a new shape and double check with stakeholders that the change fits the new landscape :  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8iq3lhegavnhn0r78gzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8iq3lhegavnhn0r78gzr.png" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" width="800" height="245"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;DevRel team proposing to shift priorities following a company strategic change&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Of course, whether the type of activities to be carried on fits DevRel well, is sustainable long term and more is another discussion altogether. The main point here is to make your priorities clear and communicate them accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  About the scale of priorities
&lt;/h3&gt;

&lt;p&gt;One question you might wonder is : How do you calculate the scale of each priority in your bar chart?&lt;/p&gt;

&lt;p&gt;Well, so far our method isn't very scientific 😊. We mostly want to show relative priorities of activities and as such we're using a set number of points and sharing them across the different axes of the chart. For example, pick 70 points (10 per angle) and subdivide. The chart then helps us make decisions quarter to quarter or day to day. Of course, when drawing other team's chart, all numbers are best effort and based on impressions.&lt;/p&gt;

&lt;p&gt;As we use these graphs more, we're experimenting with other ways to measure that could be more relevant. For example incorporating alignment and relevant weights that &lt;a href="https://www.leggetter.co.uk/aaarrrp/?ref=lengrand.fr" rel="noopener noreferrer"&gt;the original AAARRRP article&lt;/a&gt; mentions.&lt;/p&gt;

&lt;h3&gt;
  
  
  A word of conclusion
&lt;/h3&gt;

&lt;p&gt;Overall, the only thing we've been doing is taking the AAARRRP framework as an inspiration to clearly visualise what our team focusses on and bring some clarity internally. We've been using it on all of our presentations and internal pages focussing on our team's strategy.&lt;/p&gt;

&lt;p&gt;It has helped to focus discussions with stakeholders more on how much impact we can bring, rather than what activities to carry and why and it's really been useful for us.&lt;/p&gt;

&lt;p&gt;Having joined DevRelCon this year I know that internal communication is always a strong topic in DevRel teams. Maybe this can help bring some clarity on the topic!&lt;/p&gt;

&lt;p&gt;Cheers, and talk soon.&lt;/p&gt;

&lt;p&gt;Julien&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>strategy</category>
      <category>development</category>
    </item>
    <item>
      <title>Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Sat, 04 Nov 2023 17:40:33 +0000</pubDate>
      <link>https://dev.to/jlengrand/creating-an-openapi-generator-from-scratch-from-yaml-to-jetbrains-http-client-10eo</link>
      <guid>https://dev.to/jlengrand/creating-an-openapi-generator-from-scratch-from-yaml-to-jetbrains-http-client-10eo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0arnygw61oq8d1ietbb7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0arnygw61oq8d1ietbb7.jpeg" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the online version of the article with the same name I wrote for the Dutch Java Magazine 😊.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Creating your own code generator for the &lt;a href="https://twitter.com/jetbrains?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;@jetbrains&lt;/a&gt; http client from an &lt;a href="https://twitter.com/hashtag/openapi?src=hash&amp;amp;ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;#openapi&lt;/a&gt; specification? It’s possible and you can read how in the latest edition of the Dutch &lt;a href="https://twitter.com/hashtag/java?src=hash&amp;amp;ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;#java&lt;/a&gt; magazine!&lt;br&gt;&lt;br&gt;
And yes, it’s Jetbrains, not yet brains 😂 &lt;a href="https://t.co/zLGkpibLtN?ref=lengrand.fr" rel="noopener noreferrer"&gt;pic.twitter.com/zLGkpibLtN&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Julien Lengrand-Lambert 🥑👋 (&lt;a class="mentioned-user" href="https://dev.to/jlengrand"&gt;@jlengrand&lt;/a&gt;) &lt;a href="https://twitter.com/jlengrand/status/1720369690105590201?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;November 3, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the previous edition of the magazine, we discussed how the &lt;a href="https://www.jetbrains.com/help/idea/http-client-reference.html?ref=lengrand.fr" rel="noopener noreferrer"&gt;JetBrains HTTP Client&lt;/a&gt; could be used to run HTTP Queries, automate them and even use them in your CI/CD pipelines. Just like &lt;a href="https://www.postman.com/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Postman&lt;/a&gt;, but text based and can be part of your source code. Pretty cool.&lt;/p&gt;

&lt;p&gt;For reference, it could look like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Github API - Traffic per day

GET https://api.github.com/repos/{{owner}}/{{repo}}/traffic/views?per=day
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
Authorization: Bearer {{github_key}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With an environment file that looks like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "dev": {
    "github_key": "not_that_easy",
    "owner": "jlengrand",
    "repo": "elm-firebase"
  }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, that is very nice, but it requires a lot of manual work. &lt;strong&gt;Wouldn’t it be nice to be able to automate this?&lt;/strong&gt; Fortunately, most of us developing APIs also generate &lt;a href="https://www.openapis.org/?ref=lengrand.fr" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; Specifications for them. When I looked however, there was no OpenAPI generator yet available for the Jetbrains HTTP Client. This is the story of how I've implemented it from scratch, and how you could too if you find yourself in the same situation! We'll use the JetBrains HTTP Client as a practical example, but the knowledge is transferable 🙂.&lt;/p&gt;

&lt;p&gt;The OpenAPI generator project contains a core engine, as well as many packages with each a specific generator (Java, Ada, …). The Jetbrains HTTP Client generator is actually published, and you can find &lt;a href="https://github.com/OpenAPITools/openapi-generator/pull/14477/files?ref=lengrand.fr" rel="noopener noreferrer"&gt;the merge request&lt;/a&gt; as well as &lt;a href="https://openapi-generator.tech/docs/generators/jetbrains-http-client?ref=lengrand.fr" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; on GitHub. If you have installed the last available OpenAPI generator release, you can actually try it out in a terminal as such :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openapi-generator generate -i https://api.opendota.com/api -g jetbrains-http-client -o dotaClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At its core, the idea of the OpenAPI generator is quite simple : It takes a specification file (JSON or YAML), transforms it into a set of objects in memory, and uses those objects to generate code / files using &lt;a href="https://mustache.github.io/?ref=lengrand.fr" rel="noopener noreferrer"&gt;mustache&lt;/a&gt; template files.&lt;/p&gt;

&lt;p&gt;You can actually find most of that logic in the &lt;code&gt;DefaultGenerator&lt;/code&gt; source file of the library. There, you can see that actions are separated into 3 groups :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;models (basically data types)&lt;/li&gt;
&lt;li&gt;operations (actual operations)&lt;/li&gt;
&lt;li&gt;supporting files (environments, READMEs, …). &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of those is illustrated by a method, and takes separate objects as inputs :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; …
  void generateModels(List&amp;lt;File&amp;gt; files, List&amp;lt;ModelMap&amp;gt; allModels, List&amp;lt;String&amp;gt; unusedModels) {…}
…
    void generateApis(List&amp;lt;File&amp;gt; files, List&amp;lt;OperationsMap&amp;gt; allOperations, List&amp;lt;ModelMap&amp;gt; allModels) {...}
…
    private void generateSupportingFiles(List&amp;lt;File&amp;gt; files, Map&amp;lt;String, Object&amp;gt; bundle) {...}
…
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find &lt;a href="https://github.com/OpenAPITools/openapi-generator/blob/78f3b19b58df699ef883b89a7a44531407377719/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java?ref=lengrand.fr#L433" rel="noopener noreferrer"&gt;the actual source file on GitHub&lt;/a&gt;. The objects for each of those methods are large &lt;code&gt;Map&lt;/code&gt; classes that contain the necessary data in a semi-structured format. Here is an example of how &lt;code&gt;allModels&lt;/code&gt; looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FNVgYf1bDqjbDEuvOkdmDReLlE0pVj2M3A64JpNQncZmFvosFOlA4tGzb1idJWD8cWexTV-tlzd18VJwJ0lgkqHYi80GZFqmj3S-oJtXwa9Y2LrRG1mU-gdlKfBIV0hZsIBm2arP9QlVTQlfIvuwS_Tc" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FNVgYf1bDqjbDEuvOkdmDReLlE0pVj2M3A64JpNQncZmFvosFOlA4tGzb1idJWD8cWexTV-tlzd18VJwJ0lgkqHYi80GZFqmj3S-oJtXwa9Y2LrRG1mU-gdlKfBIV0hZsIBm2arP9QlVTQlfIvuwS_Tc" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="1600" height="925"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;a debug view of the allModels object&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the object is essentially a lot of key/value pairs that are quite recognisable and directly come from the OpenAPI specification file.&lt;/p&gt;

&lt;p&gt;To create our own client, we will take advantage of this nice work. Let's dive into it. We first clone the repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git@github.com:OpenAPITools/openapi-generator.git; cd openapi-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then use the &lt;code&gt;/new.sh&lt;/code&gt; script to generate a few placeholder files for us. We'll be generating a client, and since we're not creating any bugs we won't be generating test files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./new.sh -n java-magazine-client -c

Creating modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaMagazineClientClientCodegen.java
Creating modules/openapi-generator/src/main/resources/java-magazine-client/README.mustache
Creating modules/openapi-generator/src/main/resources/java-magazine-client/model.mustache
Creating modules/openapi-generator/src/main/resources/java-magazine-client/api.mustache
Creating bin/configs/java-magazine-client-petstore-new.yaml
Finished.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The library nicely generates a client generator for us, as well as some template files and even a config so we can test it easily! The config uses the well known &lt;a href="https://spring-framework-petclinic-qctjpkmzuq-od.a.run.app/?ref=lengrand.fr" rel="noopener noreferrer"&gt;petstore&lt;/a&gt; by default.&lt;/p&gt;

&lt;p&gt;This is how the config file looks like :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generatorName: java-magazine-client
outputDir: samples/client/petstore/java/magazine/client
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/java-magazine-client
additionalProperties:
  hideGenerationTimestamp: "true"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It nicely mentions to the OpenAPI generator library which generator to use, which sample OpenAPI file to use as input, where the mustache template files are located and where to store the output&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's run it!&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./mvnw clean package # package once to have the generator inside the generated jar
$ ./bin/generate-samples.sh bin/configs/java-magazine-client-petstore-new.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see what the generated output looks like :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FQdjBEGu-dpqVoX_EMfZ5DZPG9ND4g3KjY1HR6Yo0LvChQmnRAPQBpBV-MAp6HNteXRbAb4ZOjQkQs9LhxBkj9KiZ7iRFdVKNTwNyNzNjCAQUWoc4RnipISl2kJcsKpauctW-D-Atkq7J5jEOyXcsA98" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FQdjBEGu-dpqVoX_EMfZ5DZPG9ND4g3KjY1HR6Yo0LvChQmnRAPQBpBV-MAp6HNteXRbAb4ZOjQkQs9LhxBkj9KiZ7iRFdVKNTwNyNzNjCAQUWoc4RnipISl2kJcsKpauctW-D-Atkq7J5jEOyXcsA98" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="1600" height="505"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;a tree view of the generated client&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We haven't done any work yet, and our generator is already spitting out things! Unfortunately, as we can see, all those files are empty. That's because our mustache template files also are empty. Let's fix that now!&lt;/p&gt;

&lt;p&gt;We'll start by customizing the &lt;code&gt;JavaMagazineClientClientCodegen&lt;/code&gt; to fit our needs. We want a very minimal implementation that fits in this article, so we'll actually decide to NOT implement any supporting files (the README), nor Models and instead focus solely on the API. The way to do this in a custom generator is to extend the &lt;code&gt;postProcessOperationsWithModels&lt;/code&gt; from the &lt;code&gt;CodeGenConfig&lt;/code&gt; interface. We change the &lt;code&gt;.zz&lt;/code&gt; extension into &lt;code&gt;.http&lt;/code&gt; files that will be recognised by IntelliJ. And because in this specific (simplistic) case, we will not need any alterations to the &lt;code&gt;OperationsMap&lt;/code&gt; object we can actually only call the super method. Our final class looks like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package org.openapitools.codegen.languages;
import org.openapitools.codegen.*;
import java.io.File;
import java.util.*;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationsMap;

public class JavaMagazineClientClientCodegen extends DefaultCodegen implements CodegenConfig {

    public CodegenType getTag() { return CodegenType.CLIENT; }

    public String getName() { return "java-magazine-client"; }

    public String getHelp() { return "Generates a java-magazine-client client."; }

    public JavaMagazineClientClientCodegen() {
        super();

        outputFolder = "generated-code" + File.separator + "java-magazine-client";
        apiTemplateFiles.put("api.mustache", ".http");
        embeddedTemplateDir = templateDir = "java-magazine-client";
        apiPackage = "Apis";
    }

    @Override
    public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List&amp;lt;ModelMap&amp;gt; allModels) {
        return super.postProcessOperationsWithModels(objs, allModels);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note: At first glance, the method and variable names may look a bit like magic. It is because most of the logic comes from DefaultGenerator, and CodeGenConfig. If you feel lost, those two classes are where it's at.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our baseline, what we want to do is work on our mustache files. Those files are basically templates that will be fed into the processing pipeline to generate our &lt;code&gt;.http&lt;/code&gt; files.&lt;/p&gt;

&lt;p&gt;We know we want one file per main API endpoint, with some documentation. We also want the &lt;code&gt;@name&lt;/code&gt; unique identifier from the Jetbrains HTTP Client to be able &lt;a href="https://www.jetbrains.com/help/idea/exploring-http-syntax.html?ref=lengrand.fr#http_request_names" rel="noopener noreferrer"&gt;to reference our code&lt;/a&gt;. Finally, we want to add the supported content type for the calls.&lt;/p&gt;

&lt;p&gt;If we look at the data object available for operations, we end up with this, where each &lt;code&gt;{{item}}&lt;/code&gt; notation is the value of the item key inside the object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## {{classname}}
{{#operations}}
{{#operation}}

### {{#summary}}{{summary}}{{/summary}}
# @name {{operationId}}
{{httpMethod}} {{basePath}}{{path}}
{{#consumes}}Content-Type: {{{mediaType}}}
{{/consumes}}
{{/operation}}
{{/operations}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see it clearly if we look at the object during processing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FLW_g2JLAtaj1yq2LJeg0LBb0yTOy4ufSyurPNWW6XjKcdsv5GhVSASr6yWS7vYv3vmEuS9xmbZqzBa4Mqt76dg_Bv47gMoifUjxInC0-z1WkJYJRU3grz4RBApXJAZl4ZCx1irLQ69axWx5CQAe1fM0" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FLW_g2JLAtaj1yq2LJeg0LBb0yTOy4ufSyurPNWW6XjKcdsv5GhVSASr6yWS7vYv3vmEuS9xmbZqzBa4Mqt76dg_Bv47gMoifUjxInC0-z1WkJYJRU3grz4RBApXJAZl4ZCx1irLQ69axWx5CQAe1fM0" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="1600" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FHL2UN4ZvPrLgENa7ergdKqrLfcLFEp_N8I46e973y1QsKWO7nzaiqSyjypk-YAdA7_fA_HdjIJR2PwI9zWAym9tVMtSLksXMMZYVmWj6oJbr84V4A90PfMWjqQZ468lQmc9eN8CJEY8h3L4apRAGcgw" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FHL2UN4ZvPrLgENa7ergdKqrLfcLFEp_N8I46e973y1QsKWO7nzaiqSyjypk-YAdA7_fA_HdjIJR2PwI9zWAym9tVMtSLksXMMZYVmWj6oJbr84V4A90PfMWjqQZ468lQmc9eN8CJEY8h3L4apRAGcgw" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="710" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: Unfortunately, to my knowledge the best way to dive into the data model is still to go pause at runtime, I haven't yet found a complete data model documentation online. If you do, let me know!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's rerun the generation and see what we get now :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./mvnw package 
$ ./bin/generate-samples.sh bin/configs/java-magazine-client-petstore-new.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FhJ-53Q53ibGC0DuCaqu7yuoWpnLJB3d6g9SYUQ26-XeAVLW5JgavLfljBo08hnuyMwUsQo4Hgz5aBthp8L8jqFCpq1RBFWCz-PWFvpdofXDgR4o7QI_iyFKYMz4Afbet38-rEnzAuCXL4aCaL7ZUnZE" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FhJ-53Q53ibGC0DuCaqu7yuoWpnLJB3d6g9SYUQ26-XeAVLW5JgavLfljBo08hnuyMwUsQo4Hgz5aBthp8L8jqFCpq1RBFWCz-PWFvpdofXDgR4o7QI_iyFKYMz4Afbet38-rEnzAuCXL4aCaL7ZUnZE" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="1600" height="285"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;the result of the generation of our client&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Great! Only API files, and one per API, as wanted. Let's see what they contain!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## PetApi

### Add a new pet to the store
# @name addPet
POST http://petstore.swagger.io/v2/pet
Content-Type: application/json
Content-Type: application/xml

### Deletes a pet
# @name deletePet
DELETE http://petstore.swagger.io/v2/pet/{petId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks great to me! Let's try to run one of the calls&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FLrHrgXoLj4y9_lyYCzAbtLGle6eDCqv-eyDWOVQLkCkdsb2LskNOrdhBEO0c0wDMuRb9EHbh3i21TpLEcntMyd_qhHqeYgIsQDzDOD7FCrf6VNsaxe3RY1OzrzB21uqo1SxwzlF7lXZ7oqyno3FAGSo" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Flh7-us.googleusercontent.com%2FLrHrgXoLj4y9_lyYCzAbtLGle6eDCqv-eyDWOVQLkCkdsb2LskNOrdhBEO0c0wDMuRb9EHbh3i21TpLEcntMyd_qhHqeYgIsQDzDOD7FCrf6VNsaxe3RY1OzrzB21uqo1SxwzlF7lXZ7oqyno3FAGSo" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" width="1600" height="698"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Running one of the calls that's just been generated&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It works just fine, and we get a 200 response as well. Success!&lt;/p&gt;

&lt;p&gt;Now, there's only one little issue. The variables!  In the Jetbrains HTTP Client format, &lt;a href="https://www.jetbrains.com/help/idea/exploring-http-syntax.html?ref=lengrand.fr#using_request_vars" rel="noopener noreferrer"&gt;variables are written as&lt;/a&gt; &lt;code&gt;{{variable}}&lt;/code&gt;. OpenAPI implementations only have single braces. We need to fix that!&lt;/p&gt;

&lt;p&gt;What we'll be doing here is implement &lt;a href="https://mustache.github.io/mustache.5.html?ref=lengrand.fr" rel="noopener noreferrer"&gt;a custom mustache lambda&lt;/a&gt; that doubles up the braces when it finds them. The lambda is essentially a string replacement&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static class DoubleMustacheLambda implements Mustache.Lambda {
        @Override
        public void execute(Template.Fragment fragment, Writer writer) throws IOException {
            String text = fragment.execute();
            writer.write(text
                    .replaceAll("\\{", "{{")
                    .replaceAll("}", "}}")
            );
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to make it available in our Generator, the openapi generator library offers the same mechanism as for the rest : We have to override a ready-made method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    @Override
    protected ImmutableMap.Builder&amp;lt;String, Mustache.Lambda&amp;gt; addMustacheLambdas() {

        return super.addMustacheLambdas()
                .put("doubleMustache", new JavaMagazineClientClientCodegen.DoubleMustacheLambda());
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above is added to our &lt;code&gt;JavaMagazineClientClientCodegen&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Next, we also need to modify our mustache template to add that lambda at the right location (around the &lt;code&gt;path&lt;/code&gt; parameter). If that path is a variable, the braces will then be doubled&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## {{classname}}
{{#operations}}
{{#operation}}

### {{#summary}}{{summary}}{{/summary}}
# @name {{operationId}}
{{httpMethod}} {{basePath}}{{#lambda.doubleMustache}}{{path}}{{/lambda.doubleMustache}}
{{#consumes}}Content-Type: {{{mediaType}}}
{{/consumes}}
{{/operation}}
{{/operations}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voilà! Running the sample again, we can now use variables as they are meant to be inside IntelliJ!&lt;/p&gt;

&lt;p&gt;In the sample below, I'm using the following local environment file :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "dev": {
    "petId": 3
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is still a lot more to do with this generator. READMEs, payload, auth, headers, … But now it's a matter of updating the mustache files as we want.&lt;/p&gt;

&lt;p&gt;I'd love to have a more fleshed out generator, because it'd be an amazing and cheap way together with &lt;a href="https://www.jetbrains.com/help/idea/http-client-cli.html?ref=lengrand.fr" rel="noopener noreferrer"&gt;the client CLI&lt;/a&gt; to have a great automated integration tests pipeline and get people running in second with your API.&lt;/p&gt;

&lt;p&gt;I hope this article made you feel like trying to create your own OpenAPI generator. The only limit is your imagination! And as you can see, the merging process is actually relatively pleasant, because the volunteers of the project LOVE to see people bringing out new ideas to life.&lt;/p&gt;

&lt;p&gt;Happy to hear your thoughts, as always!&lt;/p&gt;

</description>
      <category>openapi</category>
      <category>jetbrains</category>
      <category>http</category>
      <category>development</category>
    </item>
    <item>
      <title>[Unit] Testing Supabase in Kotlin using Test Containers - PART 2</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Fri, 20 Oct 2023 21:16:46 +0000</pubDate>
      <link>https://dev.to/jlengrand/unit-testing-supabase-in-kotlin-using-test-containers-part-2-3i33</link>
      <guid>https://dev.to/jlengrand/unit-testing-supabase-in-kotlin-using-test-containers-part-2-3i33</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk955ihts3mcqsc6zsio6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk955ihts3mcqsc6zsio6.png" alt="[Unit] Testing Supabase in Kotlin using Test Containers - PART 2" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR : You can run a full Supabase instance inside Test Containers quite easily. &lt;a href="https://github.com/jlengrand/supabase-testcontainers-kotlin?ref=lengrand.fr" rel="noopener noreferrer"&gt;See this repository&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/jlengrand/unit-testing-supabase-in-kotlin-using-test-containers-2leg"&gt;In my last article&lt;/a&gt;, I was listing a few attempts I had done at running tests against my Kotlin Supabase application. The way the Supabase-Kt library is built makes it hard to mock, and I ended up building a minimal Docker Compose setup that was mimicking a Supabase instance.&lt;/p&gt;

&lt;p&gt;In this second part, we're gonna push the madness further and actually run a FULL SUPABASE instance locally, still using &lt;a href="https://testcontainers.com/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Test Containers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Right after I finished pushing my repository last week, I realised that Supabase actually offered &lt;a href="https://supabase.com/docs/guides/self-hosting/docker?ref=lengrand.fr" rel="noopener noreferrer"&gt;a Docker Compose file to self-host their platform&lt;/a&gt;. So I decided to push the madness further and see how easy it was to use that file inside TestContainers. In short : Relatively easy.&lt;/p&gt;

&lt;h3&gt;
  
  
  The setup
&lt;/h3&gt;

&lt;p&gt;The setup isn't actually much different from my homecrafted Docker Compose. version. Here it is in its entirety.&lt;/p&gt;

&lt;p&gt;A few notable things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I'm relying on a local clone of Supabase, and point a ComposeContainer to the &lt;code&gt;src/test/resources/supabase/docker/docker-compose.yml&lt;/code&gt; file. &lt;/li&gt;
&lt;li&gt;The setup uses an &lt;code&gt;.env&lt;/code&gt; file, so I use a &lt;code&gt;dotenv&lt;/code&gt; implementation to grab the parameters there and make the code slightly dynamic.&lt;/li&gt;
&lt;li&gt;I have to run a database statement to populate and flush my database in between tests. The Docker Compose setup from Supabase comes with persistent volumes, which needs to be accounted for. &lt;/li&gt;
&lt;li&gt;I don't have test for those here, but all services (auth, functions, storage), ... should actually be supported, given that we're running a full local instance.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import io.github.cdimascio.dotenv.dotenv
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.testcontainers.containers.ComposeContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File
import java.sql.DriverManager

@Testcontainers
class MainKtTest {

    @Test
    fun testEmptyPersonTable(){
        runBlocking {
            val result = getPerson(supabaseClient)
            assertEquals(0, result.size)
        }
    }

    @Test
    fun testSavePersonAndRetrieve(){
        val randomPersons = listOf(Person("Jan", 30), Person("Jane", 42))

        runBlocking {
            val result = savePerson(randomPersons, supabaseClient)
            assertEquals(2, result.size)
            assertEquals(randomPersons, result.map { it.toPerson() })

            val fetchResult = getPerson(supabaseClient)
            assertEquals(2, fetchResult.size)
            assertEquals(randomPersons, fetchResult.map { it.toPerson() })
        }
    }

    companion object {

        private const val DOCKER_COMPOSE_FILE = "src/test/resources/supabase/docker/docker-compose.yml"
        private const val ENV_LOCATION = "src/test/resources/supabase/docker/.env" // We grab the JWT token from here

        val dotenv = dotenv{
            directory = File(ENV_LOCATION).toString()
        }

        private val jwtToken = dotenv["SERVICE_ROLE_KEY"]
        private val dbPassword = dotenv["POSTGRES_PASSWORD"]
        private val db = dotenv["POSTGRES_DB"]

        private lateinit var supabaseClient: SupabaseClient

        @Container
        var container: ComposeContainer = ComposeContainer(File(DOCKER_COMPOSE_FILE))
            .withExposedService("kong", 8000)
            .withExposedService("db", 5432) // Handy but not required

        @JvmStatic
        @AfterAll
        fun tearDown() {
            val dbUrl = container.getServiceHost("db", 5432) + ":" + container.getServicePort("db", 5432)

            val jdbcUrl = "jdbc:postgresql://$dbUrl/$db"
            val connection = DriverManager.getConnection(jdbcUrl, "postgres", dbPassword)

            try {
                val query = connection.prepareStatement(
                    """
            drop table public.person;
        """
                )

                query.executeQuery()
            } catch (ex: Exception) {
                println(ex)
            }
        }

        @JvmStatic
        @BeforeAll
        fun setUp() {
            val supabaseUrl = container.getServiceHost("kong", 8000) + ":" + container.getServicePort("kong", 8000)
            val dbUrl = container.getServiceHost("db", 5432) + ":" + container.getServicePort("db", 5432)

            supabaseClient = createSupabaseClient(
                supabaseUrl = "http://$supabaseUrl",
                supabaseKey = jwtToken
            ) {
                install(Postgrest)
            }

            val jdbcUrl = "jdbc:postgresql://$dbUrl/$db"
            val connection = DriverManager.getConnection(jdbcUrl, "postgres", dbPassword)

            try {
                val query = connection.prepareStatement(
                    """
                create table
                    public.person (
                                    id bigint generated by default as identity not null,
                                    timestamp timestamp with time zone null default now(),
                                    name character varying null,
                                    age bigint null
                ) tablespace pg_default;
                """
                )

                query.executeQuery()
            } catch (ex: Exception) {
                println("Error is fine here. This should actually run only once")
                println(ex) // Might be fine, this should actually run only once
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve those results, a few manual steps are required. The Docker Compose file provided by Supabase uses &lt;code&gt;container_name&lt;/code&gt; parameters, &lt;a href="https://github.com/testcontainers/testcontainers-java/pull/2741?ref=lengrand.fr" rel="noopener noreferrer"&gt;which aren't supported by Test Containers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I needed to :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clone the Supabase repository locally&lt;/li&gt;
&lt;li&gt;Copy the env file&lt;/li&gt;
&lt;li&gt;Run some magic to remove &lt;code&gt;container_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Once in a while, the Supabase repository will have to be pulled &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The results
&lt;/h3&gt;

&lt;p&gt;The results are as outrageous as I expected them to be, if not more : &lt;strong&gt;All tests are running fine, though it takes almost 1 minute to run them&lt;/strong&gt;. The &lt;code&gt;ComposeContainer&lt;/code&gt; is starting no less than 12 containers (!!!) so it is to be expected.&lt;/p&gt;

&lt;p&gt;Obviously, that setup is not to be used for unit testing. That being said, I find it absolutely freaking cool to be able to recreate your complete environment locally that easily, and I'd definitely consider that an option for bigger integration tests. The confidence I didn't have with my home brewed Docker Compose file is much higher now, given that it's directly provided by Supabase. No network needed to run my tests, pretty cool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhjpx09ca7lzhqnriix1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnhjpx09ca7lzhqnriix1.png" alt="[Unit] Testing Supabase in Kotlin using Test Containers - PART 2" width="800" height="137"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Gradle running those massive tests just fine&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  What more
&lt;/h3&gt;

&lt;p&gt;My original complete intent was to build a small layer on top of the Docker Compose file, kinda like AtomoicJar does it with its &lt;a href="https://github.com/testcontainers/testcontainers-java/blob/57ca6ad4f0a6ee8bb5feaf428b5c66c7d25259c4/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java?ref=lengrand.fr#L28" rel="noopener noreferrer"&gt;modules&lt;/a&gt;. It would have been cool to have a simple interface for a Supabase instance to start, while providing a locally for starting scripts, user roles, maybe a new set of credentials, ...&lt;/p&gt;

&lt;p&gt;Here is how they describe it for NGinx for example. I would have loved to have something similar :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Rule
public NginxContainer&amp;lt;?&amp;gt; nginx = new NginxContainer&amp;lt;&amp;gt;(NGINX_IMAGE)
    .withCopyFileToContainer(MountableFile.forHostPath(tmpDirectory), "/usr/share/nginx/html")
    .waitingFor(new HttpWaitStrategy());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the implementation I've seen extend from &lt;code&gt;GenericContainer&lt;/code&gt; though, not &lt;code&gt;ComposeContainer&lt;/code&gt; so I've decided to hold that off and keep it simple for now.&lt;/p&gt;

&lt;p&gt;Could maybe be something for the future, who knows.&lt;/p&gt;

&lt;h3&gt;
  
  
  In conclusion
&lt;/h3&gt;

&lt;p&gt;That was a fun experiment, in which I've learnt more about TestContainers 😊. I'm as happy as usual with the way Supabase shows love for their users. Providing a seemless Docker Compose like this allows for a great experience. And I'm also impressed with TestContainers and how they can run such complex flaws without breaking a sweat!&lt;/p&gt;

&lt;p&gt;If anything, I'd like them to at least ignore the &lt;code&gt;container_name&lt;/code&gt; parameter if possible. I've seen many folks being blocked by it, and I can imagine many cases, like this one where people are not in control of their compose file. I don't necessarily ask for support, but an option to ignore without throwing an exception would be great.&lt;/p&gt;

&lt;p&gt;That's it folks, till next time!&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>supabase</category>
      <category>testcontainers</category>
      <category>testing</category>
    </item>
    <item>
      <title>[Unit] Testing Supabase in Kotlin using Test Containers</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Wed, 11 Oct 2023 22:01:10 +0000</pubDate>
      <link>https://dev.to/jlengrand/unit-testing-supabase-in-kotlin-using-test-containers-2leg</link>
      <guid>https://dev.to/jlengrand/unit-testing-supabase-in-kotlin-using-test-containers-2leg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmetrqoqy6p0p11mcigoe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmetrqoqy6p0p11mcigoe.png" alt="[Unit] Testing Supabase in Kotlin using Test Containers" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR : The easiest way I found to test my database service is to mimick Supabase using Docker Compose and Test Containers. &lt;a href="https://github.com/jlengrand/supabase-mock-demo-kotlin?ref=lengrand.fr" rel="noopener noreferrer"&gt;Here's the code&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In case you don't know it, I'm a big fan of &lt;a href="https://supabase.com/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;. I love that they're a viable alternative to Firebase. I love that they're &lt;a href="https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.png?ref=lengrand.fr" rel="noopener noreferrer"&gt;built on top of Open-Source&lt;/a&gt; pieces. I love &lt;a href="https://supabase.com/blog/chatgpt-supabase-docs?ref=lengrand.fr" rel="noopener noreferrer"&gt;how innovative they are&lt;/a&gt;, and &lt;a href="https://github.com/supabase/postgres_lsp?ref=lengrand.fr" rel="noopener noreferrer"&gt;how much they give back to the community&lt;/a&gt;. And as you already know it, I love &lt;a href="https://kotlinlang.org/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt; as well.&lt;/p&gt;

&lt;p&gt;Lately, I've been building a side-project which consists of a &lt;a href="https://ktor.io/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Ktor&lt;/a&gt; webapp, and uses &lt;a href="https://github.com/supabase-community/supabase-kt?ref=lengrand.fr" rel="noopener noreferrer"&gt;Supabase-Kt&lt;/a&gt; to communicate with the database. I've been looking into ways to test my Kotlin component that interacts with the database, and it's been harder than expected.&lt;/p&gt;

&lt;p&gt;In this article, I'll dive into several methods I've been looking into and why I finally decided to go for a &lt;a href="https://docs.docker.com/compose/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt; / &lt;a href="https://testcontainers.com/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Test Containers&lt;/a&gt; solution. You can check the example repository &lt;a href="https://github.com/jlengrand/supabase-mock-demo-kotlin?ref=lengrand.fr" rel="noopener noreferrer"&gt;here&lt;/a&gt; AND THE FINAL CODE &lt;a href="https://lengrand.fr/p/f6b63121-9525-4f77-a31f-f11b22f50809/#final-solution-docker-compose-and-test-containers" rel="noopener noreferrer"&gt;HERE&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I want to achieve
&lt;/h3&gt;

&lt;p&gt;Let's imagine a minimal code example that contains a &lt;code&gt;Person&lt;/code&gt; data class, and wants to save/fetch persons via a &lt;code&gt;SupabaseClient&lt;/code&gt;. It can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Serializable
data class Person (val name: String, val age: Int)

@Serializable
data class ResultPerson (
    val id: Int,
    val name: String,
    val age: Int,
    val timestamp: String
)

fun main() {
    val supabaseClient = createSupabaseClient(
        supabaseUrl = "",
        supabaseKey = ""
    ) {install(Postgrest)}

    runBlocking {
        savePerson(listOf(Person("Jan", 30), Person("Jane", 42)), supabaseClient)
    }
}

suspend fun getPerson(client: SupabaseClient): List&amp;lt;ResultPerson&amp;gt; {
    return client
        .postgrest["person"]
        .select().decodeList&amp;lt;ResultPerson&amp;gt;()
        .filter { it.age &amp;gt; 18 }
}

suspend fun savePerson(persons: List&amp;lt;Person&amp;gt;, client: SupabaseClient): List&amp;lt;ResultPerson&amp;gt; {
    val adults = persons.filter { it.age &amp;gt; 18 }

    return client
        .postgrest["person"]
        .insert(adults)
        .decodeList&amp;lt;ResultPerson&amp;gt;()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL definition of our table looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table
    public.person (
                    id bigint generated by default as identity not null,
                    timestamp timestamp with time zone null default now(),
                    name character varying null,
                    age bigint null
) tablespace pg_default;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to be able to test that our functions behave properly. For the sake of this minimal example, I've decided to filter all non adults, but you can imagine any other use case where the functions contain some business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  First attempt : Mock Supabase
&lt;/h3&gt;

&lt;p&gt;When unit testing code using third parties that I don't have control over, my first reflex is to try and mock it.&lt;/p&gt;

&lt;p&gt;It stopped being fun really quickly. The Supabase-Kt library is making use of a lot of inline function and I ended up having to  mock more and more parts of the library and never managed to get a functional tests.&lt;/p&gt;

&lt;p&gt;The short version is that because they are &lt;em&gt;as the name indicates,&lt;/em&gt; inlined, &lt;a href="https://stackoverflow.com/questions/56753396/does-mockk-support-suspend-inline?ref=lengrand.fr" rel="noopener noreferrer"&gt;inline functions cannot be mocked in Kotlin&lt;/a&gt;. So that was the end of that experiment&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MainKtTestMock&lt;/code&gt; file of my example repository reflects that attempt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainKtTestMock {

    private lateinit var supabaseClient : SupabaseClient

    @BeforeTest
    fun setUp() {

        supabaseClient = mockk&amp;lt;SupabaseClient&amp;gt;()
        val postgrest = mockk&amp;lt;Postgrest&amp;gt;()
        val postgrestBuilder = mockk&amp;lt;PostgrestBuilder&amp;gt;()
        val postgrestResult = PostgrestResult(body = null, headers = Headers.Empty)

        every { supabaseClient.postgrest } returns postgrest
        every { postgrest["path"] } returns postgrestBuilder
        coEvery { postgrestBuilder.insert(values = any&amp;lt;List&amp;lt;Path&amp;gt;&amp;gt;()) } returns postgrestResult
    }

    @Test
    fun testSavePerson(){
        val randomPersons = listOf(Person("Jan", 30), Person("Jane", 42))

        runBlocking {
            val result = savePerson(randomPersons, supabaseClient)
            assertEquals(2, result.size)
            assertEquals(randomPersons, result.map { it.toPerson() })
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the final error I encountered :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;java.lang.IllegalStateException: Plugin rest not installed or not of type Postgrest. Consider installing Postgrest within your supabase client builder
    at io.github.jan.supabase.postgrest.PostgrestKt.getPostgrest(Postgrest.kt:172)
    at MainKtTestMock$setUp$1.invoke(MainKtTestMock.kt:34)
    at MainKtTestMock$setUp$1.invoke(MainKtTestMock.kt:34)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Second attempt: Encapsulate the Supabase Client
&lt;/h3&gt;

&lt;p&gt;My second attempt was to get around the problem by encapsulating the problematic client inside a class of mine that I can then control.&lt;/p&gt;

&lt;p&gt;It can be as simple as this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class DatabaseClient(private val client: SupabaseClient){
    suspend fun savePerson(persons: List&amp;lt;Person&amp;gt;): List&amp;lt;ResultPerson&amp;gt; {
        val adults = persons.filter { it.age &amp;gt; 18 }

        return client
            .postgrest["person"]
            .insert(adults)
            .decodeList&amp;lt;ResultPerson&amp;gt;()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And my test can then look like this (see &lt;code&gt;MainKtTestSubclass&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainKtTestSubclass {

    private lateinit var client : DatabaseClient

    @BeforeTest
    fun setUp() {
        client = mockk&amp;lt;DatabaseClient&amp;gt;()
        coEvery { client.savePerson(any&amp;lt;List&amp;lt;Person&amp;gt;&amp;gt;()) } returns listOf(ResultPerson(2, "name_2", 2, "timestamp_2"))
    }

    @Test
    fun testSavePerson(){
        val fakePersons = listOf(Person("name_1", 1), Person("name_2", 2))

        runBlocking {
            val result = client.savePerson(fakePersons)
            assertEquals(2, result.size)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My main issue now is that because I have to indicate every single time what my output should be. It also just displaces the problem, because I don't really have any nice and clean way to check that my business logic works as intended, since I'm mocking it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third attempt : Ktor mock
&lt;/h3&gt;

&lt;p&gt;The main contributor of the project gave another possible workaround &lt;a href="https://github.com/supabase-community/supabase-kt/issues/298?ref=lengrand.fr" rel="noopener noreferrer"&gt;in the GitHub issue I created&lt;/a&gt; : mock the internal Ktor engine of the Supabase client.&lt;/p&gt;

&lt;p&gt;See the &lt;code&gt;MainKtTestMockEngine&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainKtTestMockEngine {

    private val supabaseClient : SupabaseClient = createSupabaseClient("", "",) {
        httpEngine = MockEngine { _ -&amp;gt;
            respond(Json.encodeToString(Person.serializer(), Person("name_1", 16)))
        }
    }

    @Test
    fun testSavePerson(){
        val randomPersons = listOf(Person("Jan", 30), Person("Jane", 42))

        runBlocking {
            val result = savePerson(randomPersons, supabaseClient)
            assertEquals(2, result.size)
            assertEquals(randomPersons, result.map { it.toPerson() })
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is actually not a bad idea, it's light and it gets the job done is a clear and readable way. Those tests are also fast to run.&lt;/p&gt;

&lt;p&gt;My main issue with this method would be that to test my business logic I'd have to dive into the received requests of the mock engine every time, which is a little cumbersome and prone to lots of maintenance.&lt;/p&gt;

&lt;p&gt;I do want to investigate it further though.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proposed solution : Test Supabase db
&lt;/h3&gt;

&lt;p&gt;Now, one semi obvious solution would be to fire up a test database in supabase itself, and test there!&lt;/p&gt;

&lt;p&gt;That'd work. I even do it to test my release deployments!&lt;/p&gt;

&lt;p&gt;It has some obvious downsides though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We couldn't be further away from unit tests, since we're testing on the cloud&lt;/li&gt;
&lt;li&gt;Tests run slower, require internet, and also require a setup database. Cleanup can also be a mess&lt;/li&gt;
&lt;li&gt;I'd be terrified to run that against the wrong database&lt;/li&gt;
&lt;li&gt;It uses my bandwidth and projects, that either are limited, or I have to pay for!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final solution : Docker Compose and Test Containers
&lt;/h3&gt;

&lt;p&gt;I had one last idea, and that's the one I've decided to stick with for now. It leverages the fact that at its core, Supabase is built on a lot of Open-Source. And when we're using the Supabase client, we're essentially interacting with a glorified PostgreSQL / &lt;a href="https://postgrest.org/?ref=lengrand.fr" rel="noopener noreferrer"&gt;postgrest&lt;/a&gt; combo!&lt;/p&gt;

&lt;p&gt;I've decided to create a Docker Compose setup that would mimick the actual Supabase production setup and connect to this instead.&lt;/p&gt;

&lt;p&gt;A few things had to be taken into account for this to work :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I had to redirect all my postgrest calls to &lt;code&gt;/rest/v1&lt;/code&gt;, which is the path that Supabase expects. So a &lt;code&gt;GET&lt;/code&gt; on &lt;code&gt;/persons&lt;/code&gt; should actually be on &lt;code&gt;/rest/v1/persons&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;postgrest&lt;/code&gt; uses &lt;a href="https://jwt.io/?ref=lengrand.fr" rel="noopener noreferrer"&gt;JSON Web Tokens&lt;/a&gt; for authentication, so we have to set that up as part of the test class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One last thing to note is that I would have to do MORE work in case I start using any of the other services of Supabase (say auth for example).&lt;/p&gt;

&lt;p&gt;The Docker Compose setup looks like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'

services:
  ################
  # postgrest-db #
  ################
  postgrest-db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
      - DB_SCHEMA=${DB_SCHEMA}
    volumes:
      - "./initdb:/docker-entrypoint-initdb.d"
    networks:
      - postgrest-backend
    restart: always

  #############
  # postgrest #
  #############
  postgrest:
    image: postgrest/postgrest:latest
    ports:
      - "3000:3000"
    environment:
      - PGRST_DB_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgrest-db:5432/${POSTGRES_DB}
      - PGRST_DB_SCHEMA=${DB_SCHEMA}
      - PGRST_DB_ANON_ROLE=${DB_ANON_ROLE}
      - PGRST_JWT_SECRET=${PGRST_JWT_SECRET}
    networks:
      - postgrest-backend
    restart: always

  #############
  # Nginx #
  #############
  nginx:
    image: nginx:alpine
    restart: always
    tty: true
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - "80:80"
      - "443:443"
    networks:
      - postgrest-backend

networks:
  postgrest-backend:
    driver: bridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that a few auxiliary files are needed for this to work. You can find everything in the &lt;code&gt;test/resources&lt;/code&gt; folder of &lt;a href="https://github.com/jlengrand/supabase-mock-demo-kotlin/tree/main/src/test/resources?ref=lengrand.fr" rel="noopener noreferrer"&gt;the example GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A short &lt;code&gt;nginx.conf&lt;/code&gt; file. &lt;/li&gt;
&lt;li&gt;An SQL file to setup the database (note that in my actual repo, this guy already exists since I need it to setup production :)).&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;.env&lt;/code&gt; file to list all my environment variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that is done, it is already possible to run &lt;code&gt;$ docker-compose up -d&lt;/code&gt; and to run your application against &lt;code&gt;localhost&lt;/code&gt; like if you were interacting with the real Supabase.  (don't forget to call &lt;code&gt;$docker-compose down --remove-orphans -v&lt;/code&gt; to kill and delete all containers once you're done).&lt;/p&gt;

&lt;p&gt;To make the magic complete, we're gonna use the power of TestContainers to run this as unit/integration tests. My final &lt;code&gt;MainKtTestTestContainers&lt;/code&gt; test class looks like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.testcontainers.containers.ComposeContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.io.File

@Testcontainers
class MainKtTestTestContainers {

    // The jwt token is calculated manually (https://jwt.io/) based on the private key in the docker-compose.yml file, and a payload of {"role":"postgres"} to match the user in the database
    private val jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXMifQ.88jCdmcEuy2McbdwKPmuazNRD-dyD65WYeKIONDXlxg"

    private lateinit var supabaseClient: SupabaseClient

    @Container
    var environment: ComposeContainer =
        ComposeContainer(File("src/test/resources/docker-compose.yml"))
            .withExposedService("postgrest-db", 5432)
            .withExposedService("postgrest", 3000)
            .withExposedService("nginx", 80)

    @BeforeEach
    fun setUp() {
        val fakeSupabaseUrl = environment.getServiceHost("nginx", 80) +
                ":" + environment.getServicePort("nginx", 80)

        supabaseClient = createSupabaseClient(
            supabaseUrl = "http://$fakeSupabaseUrl",
            supabaseKey = jwtToken
        ) {
            install(Postgrest)
        }
    }

    @Test
    fun testEmptyPersonTable(){
        runBlocking {
            val result = getPerson(supabaseClient)
            assertEquals(0, result.size)
        }
    }

    @Test
    fun testSavePersonAndRetrieve(){
        val randomPersons = listOf(Person("Jan", 30), Person("Jane", 42))

        runBlocking {
            val result = savePerson(randomPersons, supabaseClient)
            assertEquals(2, result.size)
            assertEquals(randomPersons, result.map { it.toPerson() })

            val fetchResult = getPerson(supabaseClient)
            assertEquals(2, fetchResult.size)
            assertEquals(randomPersons, fetchResult.map { it.toPerson() })
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of the magic happens at the beginning, to setup a fake Supabase URL and connect to it. Once that is done, we can write our tests as easily as ever, since we're actually interacting with an actual light Supabase clone! (For reference, the tests take about 2 seconds to run on my machine)&lt;/p&gt;

&lt;h3&gt;
  
  
  A word of conclusion
&lt;/h3&gt;

&lt;p&gt;It took me a little while to get all these tests running, but I'm very happy about the final result. It might not be the best solution for production grade apps, but the trade off of running test containers for my side project definitely makes up for the fact that I literally have no boilerplate to run and can avoid using mocks.&lt;/p&gt;

&lt;p&gt;I'll check in the future how much I can extend the docker compose image as I get to use more Supabase services. Maybe it would be nice of Supabase to offer that image themselves so we can test easily and avoid using the cloud where not necessary :).&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>kotlin</category>
      <category>docker</category>
      <category>jvm</category>
    </item>
    <item>
      <title>Building a feature platform guided by your ethos</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Wed, 04 Oct 2023 08:06:16 +0000</pubDate>
      <link>https://dev.to/adyen/building-a-feature-platform-guided-by-your-ethos-h4n</link>
      <guid>https://dev.to/adyen/building-a-feature-platform-guided-by-your-ethos-h4n</guid>
      <description>&lt;p&gt;In this blog, we discuss our decision process to build a data platform that is powerful but yet stays guided by our ethos.&lt;/p&gt;

&lt;p&gt;My wife and I are quite different in our shopping habits. She likes analysing the market trends and she’s good at deciding what she likes. She’s even better at deciding whether to buy or not — disclaimer: usually she does buy it, and then our entrance at home looks like a package pick-up point (not true, but also not entirely not-true).&lt;/p&gt;

&lt;p&gt;I, however, have a different poison. When I am looking for something, say a new set of headphones, I analyse and analyse again, and at some point I conclude that a certain product is probably the best fit for what I am looking for. Then, I check, yet again, the distance for the second choice and after that I might end up going back to the fundamental question of whether I actually needed it in the first place.&lt;/p&gt;

&lt;p&gt;I am quite frugal, so if I am not very enthusiastic about the top choice I will probably end up discarding it (and bloating those non-conversion metrics for the A/B test behind the ecommerce site that leaves the team scratching their head about what is wrong with their website).&lt;/p&gt;

&lt;p&gt;In my defence I will say that if I am sure I need something and there is a clear market winner, I buy immediately and I do not think about it anymore.&lt;/p&gt;

&lt;p&gt;The point is, choosing what to buy and when to buy it, is quite transcendental. Choosing your tech stack is a somehow similar problem. You need to understand and reflect very well on a number of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether you actually need it, or is a fun exciting but limited-value exercise (hello ChatGPT demos 🙊)&lt;/li&gt;
&lt;li&gt;How do you sweep and track the market and assess which tool or framework to adopt?&lt;/li&gt;
&lt;li&gt;Should you build or buy?&lt;/li&gt;
&lt;li&gt;How fast do you need it and whether you are sacrificing something instead?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without thorough consideration of the above, a leadership team (often proud of their choice and/or invested in another way) may spiral the team downwards in terms of productivity and motivation. That’s why it is important to give it the right amount of love, and eventually make a well-informed choice together with the technical experts.&lt;/p&gt;

&lt;p&gt;It is remarkably difficult if you end up in a situation where there’s a clear need for something better than what you have now but there is no industry standard or a clear choice. That’s where my shopper-persona would collapse, and a Feature Store, or should I say Platform (will come to that later), has been a primary example of this sort of conundrum. At Adyen we have gone through this exercise a number of times and we have learned, sometimes the hard way, how to go about these choices. It really boils down to two things that we embrace in our ethos: iterating and control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our first attempt to build a Feature Store
&lt;/h2&gt;

&lt;p&gt;Let me use an example of a use case for a feature store at Adyen. Every payment that goes through Adyen — and we do a lot of those (860 billions USD in 2022) — undertakes a journey where a number of decisions are made through an inference service fueled by a machine learning model that we have trained and deployed. We have a few instances of those services with different purposes: our risk system (is the transaction fraud or legit), our authentication service (should we authenticate the user, and if so in which way), our routing algorithm, our transaction optimiser, our retry logic and others.&lt;/p&gt;

&lt;p&gt;We are talking about a service that can take several thousands of requests per second per model and respond in less than 100 ms. The final goal of all these models is to land as many good transactions as possible in the most efficient way, without ending up in a chargeback or a retry or higher costs.&lt;/p&gt;

&lt;p&gt;Now, these models need features, of course. They need features both at training time and at scoring time.&lt;/p&gt;

&lt;p&gt;Let’s zoom in on the risk system. The service was initially built through rules across three different data sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block/allow look-up-tables, powered by PostgreSQL.&lt;/li&gt;
&lt;li&gt;Velocity database, powered by PostgreSQL, and able to provide information such as how many times has this card been used in this last minute.&lt;/li&gt;
&lt;li&gt;Our “shopper” database, also powered by PostgreSQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “shopper” database deserves its own paragraph. We have used, and maybe even abused, PostgreSQL in a very beautiful way: to identify shoppers in real time. This system in the end provides an elegant and simple graph algorithm by identifying communities of attributes (cards, emails, …) that relate to the same person. It does that very efficiently and very fastly, but it has its own complications around flexibility and scalability. My colleague Burak &lt;a href="https://www.adyen.com/blog/the-adyen-way-of-engineering-oss-or-built-in-house" rel="noopener noreferrer"&gt;wrote a great article about it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When introducing a new approach based on a supervised classifier, we thought: hey, let’s include features about the merchant, that’ll boost AUCPR. Small note, a merchant in the fintech jargon is a seller or a company, take for example, Uber or Spotify. In this sense then, we’d include, as a feature, datapoints like the size of the merchant, the country of the merchant, for how long have we been processing for this merchant, the authorisation rate of that merchant across different sliding windows and so on.&lt;/p&gt;

&lt;p&gt;However, many of these example features are slow-moving, medium cardinality, high data volume, features. And PostgreSQL wasn’t gonna cut it in terms of crunching all that volume. So we added a new database called “Feature Store” (read that as Dr. Evil with his famous quote hands).&lt;/p&gt;

&lt;p&gt;We took advantage of the fact that we were sending all our transaction events to our Big Data Platform via Kafka. We collect all this info in the form of Hive tables and then we use Spark to crunch all this information, all beautifully orchestrated through Airflow. We had all this information and tooling available in our Big Data Platform because that’s where we train our models. We just need to use an abstraction layer to define the features (we chose Feast) and then deploy on another posgresql instance in the real-time flow (we love postgresql, in case you haven’t figured this out yet). The ML artifacts are deployed to our real-time platform through our wrapper around MLflow, called Alfred, that allows us to stage their rollout into ghost, test, canary live settings and default live modes.&lt;/p&gt;

&lt;p&gt;The final picture to our first crack at a “Feature Store” looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fba82uelwor9tiuo3zeen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fba82uelwor9tiuo3zeen.png" alt="Image description" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And happy we were ever after, thinking that we had a “Feature Store”. And by “ever after” I mean a couple of months.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build-vs-buy tradeoff
&lt;/h2&gt;

&lt;p&gt;Hidden in all this there’s something that might have passed unnoticed, the redux of a very important lesson that we have learned through the years: our build-vs-buy trade-off.&lt;/p&gt;

&lt;p&gt;There are great vendors that promise and deliver a seamless turn-key experience: it’s fast and it just “works”. It’s a very amenable choice, and I can only show my honest respect for these startups: they are like rain in the middle of a drought for a lot of companies that want to instantly get in the gig of Feature Stores, MLOps, Experimentation, Data Governance and any other sweet problem to solve. And they do a good job at it.&lt;/p&gt;

&lt;p&gt;At Adyen, we like to stay in control and understand what happens under the hood. We also have a very solid principle: we won’t use a vendor for anything touching our core business. In this case, processing a payment is indeed core business and we don’t want to introduce a dependency on a third-party. That has two important implications that are worth calling out:&lt;/p&gt;

&lt;p&gt;First, we do use vendors, but we are critical about which part of the system they impact. On the one hand we buy our laptops from a vendor — it wouldn’t be optimal if we had a team building laptops. On the other hand, because we are set in building for the long term, we believe in controlling all our supply-chain (take SpaceX, procuring their own screws even).&lt;/p&gt;

&lt;p&gt;Second. If we don’t use vendors, do we then build in-house? We have made that choice in the past, and now in perspective, it was a mistake. Picture a top-performing engineer, machete in their teeth, mumbling a classical “hold my beer” and then proceeding to build from scratch something that already exists because “it will only take me a week to do it better”.&lt;/p&gt;

&lt;p&gt;We have been there and after the first month I can guarantee the fun is over. Looking ahead at the feature parity roadmap sinks you in despair, the operational debt and preventable bugs itch you more than usual, and you end up going to bed every night thinking “why did we do this”.&lt;/p&gt;

&lt;p&gt;So what’s the answer?&lt;/p&gt;

&lt;p&gt;Well, open source.&lt;/p&gt;

&lt;p&gt;We use open source as much as possible to build our infra and rails, and then we build on top of it our core business. We also contribute back by merging PRs and adding new features that we found useful. At the end of the day, the internet runs on open source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterating on the Feature Store
&lt;/h2&gt;

&lt;p&gt;I already gave away one bit of our ethos: strong control of our dependencies, fueled by long-term thinking. A second big trait of our way of thinking is the iteration culture.&lt;/p&gt;

&lt;p&gt;At some point when building software, and even hardware systems, someone figured out that working in waterfall contracts doesn’t really help and that instead working in an agile way gets you further and faster, and it is also more fun. The point of agile is not to adopt scrum. The point of agile is to embrace that the MVP is minimum (and therefore rusty and barely presentable) and also viable (it works, it’s not a WIP commit), and from there onwards, you have to iterate and quickly.&lt;/p&gt;

&lt;p&gt;We took a cold look at what we had built that we proudly called “feature store”, tried to remove any emotional attachments to it, and ended up concluding that it wasn’t actually great. We also conclude that we might want to do some soul-searching and write a requirement list about what we want the whole thing to do.&lt;/p&gt;

&lt;p&gt;We ended up with a letter to Santa with everything we wanted. At least from there we could make a conscious choice about what we will not get given the cost and possibilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature Parity: the features and values on the training and scoring flows must be identical.&lt;/li&gt;
&lt;li&gt;Retrieval latency: we need the inference service to work under 100 ms, so there’s that.&lt;/li&gt;
&lt;li&gt;Recency: the features should not be old, we should be able to refresh them very fast.&lt;/li&gt;
&lt;li&gt;Cardinality: we want to be able to store billions of features.&lt;/li&gt;
&lt;li&gt;Distributed: we need instances of the feature store around the world, because we process globally.&lt;/li&gt;
&lt;li&gt;Storage / Scalability: our transaction volume grows quite a lot every year, and we build for 20x.&lt;/li&gt;
&lt;li&gt;Availability / Uptime: we need the system to be there 99.99999% of the time.&lt;/li&gt;
&lt;li&gt;Self-service: ideally we want data scientists to go about themselves when prototyping and deploying new features&lt;/li&gt;
&lt;li&gt;Complex calculation: some of these features can be complex to compute, that should be alright.&lt;/li&gt;
&lt;li&gt;Feature diversity: it’d be great if we didn’t have to maintain three different databases and we just had one endpoint with all sorts of data inside.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After seeing that list we thought “wow, it’s a long list” but we also figured that there was an underlying difference between a pure storage place and a place where things are computed. And that’s also where we read &lt;a href="https://huyenchip.com/2023/01/08/self-serve-feature-platforms.html" rel="noopener noreferrer"&gt;Chip Huygen’s fantastic article on Feature Platforms&lt;/a&gt;, and it was one of those click/a-ha moments, when you confidently say out loud “we were building a feature platform, not a feature store” and you can hear the non-existent triumphant music behind you.&lt;/p&gt;

&lt;p&gt;The main difference lies in facilitating the computing of features, apart from the storing and serving which is indeed captured under the definition of the feature store.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new blueprint
&lt;/h2&gt;

&lt;p&gt;Based on this we also saw the need to have a system that spans across two different platforms, our real-time platform (where payments, KYC, payouts, refunds and financial interactions with the world happen) and our big data platform (where we crunch the data). That’s not a surprise given that you have a need in two very different flows: your inference flow in real-time and your training flow off-line.&lt;/p&gt;

&lt;p&gt;We needed an abstraction layer that would glue both systems and we chose to keep on using Feast, the open source package to allow data scientists and engineers to define features uniquely and ensure consistency across the two environments. We also evaluated LinkedIn’s Feather, but we deemed it too opinionated and opted for the openness of Feast.&lt;/p&gt;

&lt;p&gt;The general idea behind the synching happening on the two environments is that some features will be computed on the real time flow and stored on hot storage and sync back to the cold storage (big data platform) while the slow-moving features will be computed on the big data platform, stored there (cold storage) and synched to the hot storage for inference.&lt;/p&gt;

&lt;p&gt;While the batch computation engine and storage were already there, Spark and Hive/Spark/Delta, we still had a few choices to make regarding the on-line flow. For stream computing we are inclined to use Apache Flink — but we can define it later. For the storage layer we hit a dilemma across a few contenders: Redis, Cassandra, Cockroach and sweet old posgresql.&lt;/p&gt;

&lt;p&gt;Here is where I will circle back to where I started: you need to make good decisions and that probably means that you need to involve technical experts. Even there, you also want to be able to tap into the wider organisation to make sure that you are not too biassed or forgetting anything. That’s why we have a TechRadar procedure where engineers can share ideas for technology contenders, spar, benchmark and eventually decide on which to adopt in a “makes sense” fashion.&lt;/p&gt;

&lt;p&gt;We decided for Cassandra. Redis’ in-memory storage makes it quite expensive at the cardinality we are looking for, Cockroach is really keen on read-write consistency at the expense of speed and posgresql, well, didn’t cut it for our needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F275g7b6gluu04l8e5w35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F275g7b6gluu04l8e5w35.png" alt="Image description" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We still are evaluating choices for on-line computing engines (as said, Flink looks good) and feature monitoring, where it might be that we just use the monitoring stack available, largely consisting of Prometheus, Elastic and Grafana.&lt;/p&gt;

&lt;p&gt;That’s an honest look at where we are today. We are making an informed choice and not shooting from the hip. We have determined what we need, we have also determined what is important to us and what we are willing to pay for it. We have analysed the market and open-source offering and are back to our beloved execution mode.&lt;/p&gt;

&lt;p&gt;Even if there’s no clear and obvious choice, my shopper persona is still happily going through this procurement journey and enjoying the benefits of learning, discovering the possibilities and deciding. Because if we don’t get it right at first, we will build, fail, learn and iterate.&lt;/p&gt;

</description>
      <category>database</category>
      <category>datascience</category>
      <category>platforms</category>
      <category>ai</category>
    </item>
    <item>
      <title>Thoughts on the Climate crisis and being a Developer (Advocate)</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Tue, 05 Sep 2023 17:10:08 +0000</pubDate>
      <link>https://dev.to/jlengrand/thoughts-on-the-climate-crisis-and-being-a-developer-advocate-464l</link>
      <guid>https://dev.to/jlengrand/thoughts-on-the-climate-crisis-and-being-a-developer-advocate-464l</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71uert5pn1r0kn0a1vvc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71uert5pn1r0kn0a1vvc.jpg" alt="Thoughts on the Climate crisis and being a Developer (Advocate)" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This blog is not meant to judge anyone, nor to make anyone feel guilty in any way. Apologies if it comes out the wrong way.&lt;/p&gt;

&lt;p&gt;What I mean to share here is a &lt;strong&gt;personal&lt;/strong&gt; journey we've embarked on as a family a couple years back. I do realise how privileged we are to be able to have such a position. Oh and by the way I'm always looking for ways to do better, so please do voice your opinion if you have good ideas.&lt;/p&gt;

&lt;p&gt;It's quite clear by now that we humans have a strong impact on the Earth's  climate. And it's also quite clear things aren't changing as fast as they need to to make a meaningful difference globally.&lt;/p&gt;

&lt;p&gt;As a &lt;em&gt;new&lt;/em&gt; Developer Advocate (I've only been in the field for two years officially), my impact on the world around me is something I keep in the back of my head pretty much every day. I want to go with you through some of these thoughts today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advocating through individual actions?
&lt;/h2&gt;

&lt;p&gt;As an individual, there are a few directions you can go to reduce your impact on climate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Individual actions&lt;/strong&gt; : That's the easiest thing to do, but also what has the least amount of impact. Reducing your footprint might feel good, but it doesn't really help in the grand scheme of things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group actions&lt;/strong&gt; : Voting, supporting political groups, protesting, boycotting, ... Basically helping change the system any way you can as a part of society. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thing is, part of my job as a Developer Advocate is to be public. Of course, I'm not a Kylian Mbappé, or a Rihanna, ... I'm pretty much a nobody. But I still have a voice in certain niches. Through my job I am spending some of my time in online communities. I like to believe that my individual actions (and those of my family) can ripple to others because I have a few means of promoting them around me.&lt;/p&gt;

&lt;p&gt;In this blog, I will focus on the individual actions that I do to reduce my impact on the climate. I'll also limit myself  to the ones that have a relationship with my job as Developer Advocate. I won't go into investing or producing food for example. I have enough other blogs on these topics 😊.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traveling, conferences and the airport's life
&lt;/h2&gt;

&lt;p&gt;Most of the Developer Advocates I meet are public folks. They travel A LOT. If you want to maximize your presence as a Developer Advocate, you want to participate in as many events as possible; which typically means flying. The funny thing is, often the public figures are the same from one conference to the next; meaning that as a speaker 60% of the folks you meet are the same in Germany as they were in UK the week before.&lt;/p&gt;

&lt;p&gt;I can't help but think how destructive we are as a group living this completely unsustainable way of living. "it's part of the job". When I see " ATL ✈️ CDG ✈️ TYO✈️ WLG ✈️  AMS ✈️ ATL" on my favourite social media, the first thing I think is that this flight path alone would place me &lt;a href="https://bonpote.com/en/why-stop-flying-to-tackle-climate-change/?ref=lengrand.fr" rel="noopener noreferrer"&gt;in the top 1% of flyers worldwide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I've managed to not board a plane since 2019. Not for holidays, not for work. If I go to a conference, I'll take the train to go there. If I can't, well then I'll either send someone who lives closer, or simply not go. And it's not even a sacrifice, actually. I love the train. I can work in it, read books, no security gates to go through, or rough passport control, not dumb dumb 2 hours before rule, no shoes removal involved. The only thing I may miss is the mini bar, but that's a me thing 🍾.&lt;/p&gt;

&lt;p&gt;I'm not saying I'll never fly. That'd be hypocritical. For example I dream of visiting Korea. But I want to reduce it as much as I can, for as long as I can because it's by far the easiest way to cut my emissions. Flying accounted for 40% of my yearly footprint until 2019. And I wasn't a Developer Advocate yet back then. This is something I made clear when joining my current employer, and they agreed. If we have to go to many conferences overseas, we'll hire a developer advocate over there to do the work.&lt;/p&gt;

&lt;p&gt;And to be fair, it's not like our company suffers from this. Instead of travelling, we're releasing more samples, more content, more experiments, and I sincerely think we're simply serving our customers better. Most of them don't go to conferences. They want stuff that works well, with a good developer experience, that is well documented, and feel welcome with the product. Heck, it's even more inclusive in a way : Not everyone can afford to take days off and travel to go and see you. Online content is available for everyone to see, and it's more scalable by definition. Of course, this is personal experience and your mileage may vary.&lt;/p&gt;

&lt;p&gt;One of the things that infuriates me though, is that even to this day flying is the cheapest option anywhere. Taking the train from Amsterdam to Barcelona takes me 2 days and costs 400 euros, while flying there is 30 euros and only 2 hours. Our governments have to do better...&lt;/p&gt;

&lt;p&gt;One last thing : Until about 100 years ago, we've never been used to travel that much. People would do one trip to Italy in their life, and be amazed. Travelling has become a complete commodity. But we've also never been as connected as today! Phones, internet, apps, MOOCs, videos, ... There are so many ways to stay in contact with each other. There has to be better options than flying somewhere to do my advocacy!&lt;/p&gt;

&lt;p&gt;The picture is not all dark though, I've seen several people doing so much better than me, like Liz Rice biking to go to a conference in April 🤯. There are many other folks as well taking the train to travel.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is anyone thinking about cycling from London to Amsterdam for &lt;a href="https://twitter.com/hashtag/kubecon?src=hash&amp;amp;ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;#kubecon&lt;/a&gt; in April? 🤔🚴‍♀️&lt;/p&gt;

&lt;p&gt;— Liz Rice 🐝 (&lt;a class="mentioned-user" href="https://dev.to/lizrice"&gt;@lizrice&lt;/a&gt;) &lt;a href="https://twitter.com/lizrice/status/1612808632982441985?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;January 10, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  About conference planning
&lt;/h2&gt;

&lt;p&gt;Conference planning is a hard problem. Most people go to only a few conferences a year. And they're going to the most appealing ones. If you can get huge names to come to your conference, it's typically a nice way to ensure your tickets are sold. I hear that problem very much, especially post COVID where many conference organisers have piled up huge costs due to unexpected cancellations.&lt;/p&gt;

&lt;p&gt;At the same time, I have seen some things at conferences that sometimes disturbed me. Without having to be political, conferences are definitely a place where organisers can send a strong message. The huge majority of the talks I have given that were climate related either happened in the very first slot of the morning before most people wake up, or in the last slot while most of the attendees were already enjoying some beer.&lt;/p&gt;

&lt;p&gt;In one instance, I gave a climate related talk at 8h in the morning. That talk was followed by 3 cryptos talks. In another case, I've seen someone give a "reduce your CO2 emissions in the cloud" talk. That person flew all the way from South America to Europe to give a talk to 40 people. As much as I appreciated the chat, I found it hard to believe that one of the main cloud companies in the world didn't have someone in Europe at the ready to give that talk. That hampered the message of the speaker by a lot to me.&lt;/p&gt;

&lt;p&gt;Again, I'm not complaining at all about my spot in the conferences here. There are gazillions of speakers better than me 😊. My main point is that the programmation of talks is definitely sending a message to a huge amount of humans at the same time. Conferences aren't typically very clean events in terms of emissions or amount of waste produced. Maybe we can make them matter even more?&lt;/p&gt;

&lt;p&gt;The past years, I've been part of redaction commissions for magazines, and in the review committee of a few conferences. Without being completely biased, that's one of the places where I can help sending a message. Having more local speakers, inviting speakers with strong topics on stage, .... (And this goes for much more than climate by the way, diversity, inclusion, ...).&lt;/p&gt;

&lt;p&gt;Again, the picture is not completely dark. I've seen great examples of conferences who really outdid themselves to make sure that all the messaging was aligned from one end to the other. There are many great examples, but the best I have seen is &lt;a href="https://devfest.gdglille.org/?ref=lengrand.fr" rel="noopener noreferrer"&gt;DevFestLille&lt;/a&gt;. Vegetarian options by default, with reusable cutlery, an adapted programmation, speaker dinner in a local restaurant using locally produced food, sustainable gifts and more... They go much deeper than this, putting accessibility and inclusivity at the core of their values. I admire that conference. Check it out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nous clôturons le &lt;a href="https://twitter.com/hashtag/DevfestLille?src=hash&amp;amp;ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;#DevfestLille&lt;/a&gt; avec &lt;a href="https://twitter.com/fs0c131y?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;@fs0c131y&lt;/a&gt; qui nous parle d'empreinte numérique ! &lt;a href="https://t.co/d4gFq7X0Sj?ref=lengrand.fr" rel="noopener noreferrer"&gt;pic.twitter.com/d4gFq7X0Sj&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Devfest Lille (@DevfestLille) &lt;a href="https://twitter.com/DevfestLille/status/1662121748177776642?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;May 26, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Who to work for?
&lt;/h2&gt;

&lt;p&gt;Here's another one of my main struggles: As a Developer Advocate, you're by definition at the forefront of your company and how it presents itself to developers. As such, it is crucial that you're in line with the values they represent.&lt;/p&gt;

&lt;p&gt;I am fortunate with the fact that this is something that I really find at Adyen today. Of course, Adyen is here to make profit, but that's obviously OK and I'm definitely here to help. What's important (for me) is that it does it in an ethical, sustainable way. And in this sense it does much more than the large majority of the companies I've been working for. I don't want to blatantly sound like I'm promoting Adyen here, but "building an ethical business and driving sustainable growth for our merchants" is one of the core principles of Adyen since its inception. And it also adds words to action. For example Adyen dedicates 1% of its net revenue to programs that are meant to positively impact ESGs in line with the UN &lt;a href="https://sdgs.un.org/?ref=lengrand.fr" rel="noopener noreferrer"&gt;SDG&lt;/a&gt; commitment. And it combines global action like this, with &lt;a href="https://www.adyen.com/social-responsibility?ref=lengrand.fr" rel="noopener noreferrer"&gt;local actions on the ground&lt;/a&gt;. It also accomodates with my travel choices, as long as it doesn't impact my work. Of course, it's always possible to do more. But it's more than I've ever seen in my career so far.&lt;/p&gt;

&lt;p&gt;There's a whole lot of other jobs in DevRel in the industry and I can't say that I associate with all of them. There's been a large amount of advocacy positions open in Web 3, Crypto and NFT related tech lately for example. Historically, those technologies are not particularly known for their clean or efficient computing. Finding positions that line up with my values, is something that I personally struggle with at times,  &lt;/p&gt;

&lt;p&gt;I do see some VERY interesting positions as well though. For example, I admire &lt;a href="https://asim.dev/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Asim Hussain&lt;/a&gt;'s work and the &lt;a href="https://greensoftware.foundation/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Green Software Foundation&lt;/a&gt; he founded. Most cloud providers are heavily invested in reducing their and their customer's footprints. That's partly why they love your serverless workloads so much 😅.  &lt;/p&gt;

&lt;p&gt;Another example who brightened my day a while back is Jamund, finding his way into sustainability at AWS :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Super excited to announce that *today* I started with AWS here in London as a Sr. Frontend Engineer with a focus on supply chain and sustainability 🚚⛓📊🌎&lt;/p&gt;

&lt;p&gt;— Jamund Ferguson (&lt;a class="mentioned-user" href="https://dev.to/xjamundx"&gt;@xjamundx&lt;/a&gt;) &lt;a href="https://twitter.com/xjamundx/status/1566823072371458049?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;September 5, 2022&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have been looking for opportunities in the sustainability domain, but fact of the matter is there doesn't seem to be much yet for Developer Advocates (and DevRel in general).&lt;/p&gt;

&lt;h2&gt;
  
  
  We're in a great position to challenge the status quo
&lt;/h2&gt;

&lt;p&gt;It may not be true for everyone, but turns out most of us are compensated very well. Of course, I'm not saying we're rich. There's still rent to pay, tuition for the kids, supporting folks around you, ... Especially given how shaky the industry has been the past year. I'm not saying any of us has it easy. That being said, looking at &lt;a href="https://wid.world/income-comparator/?ref=lengrand.fr" rel="noopener noreferrer"&gt;the world inequality database&lt;/a&gt; humbled me. I am in a favored position to make a difference on the market, simply because I have enough wealth to have all my major needs fulfilled and I have a platform available to me.&lt;/p&gt;

&lt;p&gt;I was impressed to see the impact that many Developer Advocates did supporting Ukraine the past year for example. People like &lt;a href="https://twitter.com/alina_yurenko?ref=lengrand.fr" rel="noopener noreferrer"&gt;Alina&lt;/a&gt; are inspiring. As I was saying above, &lt;a href="https://asim.dev/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Asim&lt;/a&gt; is also a great example for substainability. As shown in the past, we are in a position to help make a difference.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Educating yourself, and listening to others
&lt;/h2&gt;

&lt;p&gt;One of the nice things of being a Developer Advocate is that whether physically or virtually, you get to meet a lot of people. And your job is pretty much to learn new stuff and share it. I like that as part of my profile I get to share some of the things I've learnt about climate and sustainability. I've given talks on the topic, I've raised awareness internally and externally. I've been sharing our journey online.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is getting completely out of hands, but there isn't a single book in that pile that I'd remove 😊. Looking forward reading them all cover to cover! &lt;a href="https://twitter.com/hashtag/books?src=hash&amp;amp;ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;#books&lt;/a&gt; &lt;a href="https://t.co/tuofRLwaLq?ref=lengrand.fr" rel="noopener noreferrer"&gt;pic.twitter.com/tuofRLwaLq&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;— Julien Lengrand-Lambert 🥑👋 (&lt;a class="mentioned-user" href="https://dev.to/jlengrand"&gt;@jlengrand&lt;/a&gt;) &lt;a href="https://twitter.com/jlengrand/status/1691828793109774774?ref_src=twsrc%5Etfw&amp;amp;ref=lengrand.fr" rel="noopener noreferrer"&gt;August 16, 2023&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, &lt;a href="https://twitter.com/ClimateHuman?ref=lengrand.fr" rel="noopener noreferrer"&gt;many&lt;/a&gt; are &lt;a href="https://twitter.com/ClimateAd?ref=lengrand.fr" rel="noopener noreferrer"&gt;doing&lt;/a&gt; it much &lt;a href="https://twitter.com/BonPote?ref=lengrand.fr" rel="noopener noreferrer"&gt;better&lt;/a&gt; than me, and I am very well aware that posting photos of potatoes won't fix the climate crisis. But I've met people along the way who gave me energy to do more, try new things, learn new stuff. We animated a panel last years with 3 other folks on sustainability in tech. I've helped run an online conference dedicated to &lt;a href="https://simplewebconf.com/?ref=lengrand.fr" rel="noopener noreferrer"&gt;Simpler and Cleaner Tech&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In France we say "les petits ruisseaux font les grandes rivières" ("They say that small streams make big rivers."). My only goal is to be surrounded by many more of those little streams 😊. Not to feel good about myself and not feel guilty. But more as a way to do ever better every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  But why do this job at all then?
&lt;/h2&gt;

&lt;p&gt;Some of you might ask "but after all those issues, why be a Developer Advocate at all then"? And that'd be a great question. I ask it to myself every day.&lt;/p&gt;

&lt;p&gt;And the answer is relatively short as well : &lt;a href="https://en.wikipedia.org/wiki/Ikigai?ref=lengrand.fr" rel="noopener noreferrer"&gt;because I absolutely love my job to bits, and it took me years to find something that I liked, that someone would pay me for, and that I'd be good at&lt;/a&gt;. I do think that we're also something the world needs. Human seek connection, and we have a role to play there. Given all of the positive examples I've given above from all those inspiring folks, I  do see a way to do my job in a way that lines up with my internal voice. You all inspire me to do better every day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobpgm2mbikgri1q81o87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobpgm2mbikgri1q81o87.png" alt="Thoughts on the Climate crisis and being a Developer (Advocate)" width="730" height="730"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A photo of Ikigai (&lt;a href="https://www.japan.go.jp/kizuna/_src/7994686/ikigai_japanese_secret_to_a_joyful_life_pic.png?v=1693289036040" rel="noopener noreferrer"&gt;https://www.japan.go.jp/kizuna/_src/7994686/ikigai_japanese_secret_to_a_joyful_life_pic.png?v=1693289036040&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's it for now. I really hope this blog doesn't come as moralizing; that really wasn't the point, &lt;a href="https://www.theguardian.com/books/2023/sep/04/the-big-idea-how-can-we-live-ethically-in-a-world-in-crisis?ref=lengrand.fr" rel="noopener noreferrer"&gt;quite the opposite&lt;/a&gt;. I'll be at DevRelCon next week, maybe we can chat there (or in the train on the way 😛). Or any other time, you can hit me on &lt;a href="https://twitter.com/jlengrand?ref=lengrand.fr" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://mastodon.online/deck/getting-started?ref=lengrand.fr" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt;, or by subscribing :).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;And if you've recently been laid off, and are still searching for a new gig; I'm so sorry. The industry hasn't been great for us the past year. If that helps I know that Adyen is searching for a &lt;a href="https://careers.adyen.com/vacancies/5152703-team-lead-internal-developer-advocate?ref=lengrand.fr" rel="noopener noreferrer"&gt;Team Lead Internal Developer Advocacy&lt;/a&gt; and a friend of mine at DHL is searching for an internal Developer Advocate in the Netherlands to kickstart their advocacy program. Hit me up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Cheers,&lt;/p&gt;

&lt;p&gt;Julien&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>climate</category>
      <category>tech</category>
    </item>
    <item>
      <title>My KotlinConf experience</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Mon, 01 May 2023 09:42:07 +0000</pubDate>
      <link>https://dev.to/jlengrand/my-kotlinconf-experience-45mf</link>
      <guid>https://dev.to/jlengrand/my-kotlinconf-experience-45mf</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5re5sqpao72d7klrtyc8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5re5sqpao72d7klrtyc8.jpeg" alt="My KotlinConf experience" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The past week, &lt;a href="//kotlinconf.com/"&gt;KotlinConf&lt;/a&gt; took place in Amsterdam. It was my very first KotlinConf ever, and to be fair I had been excitingly waiting for it for a long time 😊.I'll be trying to share some of my excitement as well as my impressions here. This post is obviously biased, for official news I recommend you directly go to the source!&lt;/p&gt;

&lt;h2&gt;
  
  
  The community
&lt;/h2&gt;

&lt;p&gt;The first and main reason for me to go to the conference was to meet all the community again, as always. I've had a glimpse of it a couple months back at the &lt;a href="https://twitter.com/jlengrand/status/1622577024832270339" rel="noopener noreferrer"&gt;GDE summit&lt;/a&gt;, but even more people were there this time 😊.&lt;br&gt;&lt;br&gt;
Many known faces of course, from &lt;a href="https://duckduckgo.com/?t=ffab&amp;amp;q=louis+cad&amp;amp;ia=web" rel="noopener noreferrer"&gt;Louis&lt;/a&gt; to &lt;a href="https://twitter.com/_JamesWard" rel="noopener noreferrer"&gt;James&lt;/a&gt;, &lt;a href="https://twitter.com/jlengrand/status/1622577024832270339" rel="noopener noreferrer"&gt;Duncan&lt;/a&gt;, &lt;a href="https://twitter.com/trisha_gee" rel="noopener noreferrer"&gt;Trisha&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/amanda-hinchman-dominguez-4190a7a4/" rel="noopener noreferrer"&gt;Amanda&lt;/a&gt;, who was also speaker at the very first conference I spoke at (in Belarus, how times change...) but also so many people I know online for a long time and who I finally got to meet. &lt;a href="https://twitter.com/jlengrand/status/1647181693571198976" rel="noopener noreferrer"&gt;Isa&lt;/a&gt;, a content creator from Spain, Sebastian, which content I follow not only because of the Kotlin magic but also to learn more about the craft of content creation itself. And I &lt;em&gt;finally&lt;/em&gt; got to meet Margaryta, who introduced me to the GDE program and came with the whole family (seriously though, more kids at conferences!)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgjgzo3dngj57uauxhf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgjgzo3dngj57uauxhf3.png" alt="My KotlinConf experience" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Meeting up with Isa, we've been talking to each other on Twitter for a long time ^^&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Somehow, I still didn't manage to Josh and Svetlana ^^. That'll be for another time.&lt;/p&gt;

&lt;p&gt;And of course, there's all the new folks. Most notably &lt;a href="https://www.linkedin.com/in/salihgueler/" rel="noopener noreferrer"&gt;Salih&lt;/a&gt;, we nerded about developer advocacy and leadership.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new stuff
&lt;/h2&gt;

&lt;p&gt;The Keynote announcements were quite exciting to be honest. From the announcement of &lt;a href="https://blog.jetbrains.com/kotlin/2021/04/kotlin-kernel-for-jupyter-notebook-v0-9-0/" rel="noopener noreferrer"&gt;Kotlin Notebooks&lt;/a&gt; (been wishing for this for a long time, try them out!), to obviously the &lt;a href="https://blog.jetbrains.com/kotlin/2023/02/k2-kotlin-2-0/" rel="noopener noreferrer"&gt;K2 compiler&lt;/a&gt; and much of the goodies that come with it. The notebooks announcement (as well as some of the ML/AI related talks at the conference) also hint at Kotlin becoming a potential alternative for Python for ML engineers out there in the future. Why not?&lt;br&gt;&lt;br&gt;
I care a lot about the language features but even more about the ecosystem, and seeing more companies joining the &lt;a href="https://kotlinfoundation.org/" rel="noopener noreferrer"&gt;Kotlin Foundation&lt;/a&gt; was the best news of the day to me. It is a great step for a long living healthy language and I'm sure it'll help drive adoption further.&lt;/p&gt;

&lt;h2&gt;
  
  
  The venue
&lt;/h2&gt;

&lt;p&gt;I really liked all of the sponsors that were present at the event. The venue was also dope for me, with a large &lt;strong&gt;Jetbrains&lt;/strong&gt; booth in the middle stuffed with Product and Engineering folks. After all, Kotlin is a creation of Jetbrains and having the product smack in the middle of everyone with demos running at all times was super cool. Of course, you know me as a big Jetbrains fan so it was really a nerd fest for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvbll4lee5g83ph8ek5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvbll4lee5g83ph8ek5w.png" alt="My KotlinConf experience" width="768" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The massive Jetbrains booth at the centre of the venue&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sponsors also made a lot of sense, and I liked that they were from various industries and domains. Hardware, Multiplatform, server, small and big companies, they were all represented and that was really cool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynrxzlcstm1b1hdt1181.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynrxzlcstm1b1hdt1181.png" alt="My KotlinConf experience" width="768" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Meeting the guys from KodeinKoders, with whom I've interacted online for a while&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Quite a few of the companies who also have a large impact on the community via their contributions made it a strong part of their booth and I liked it a lot. From Xebia (47deg) with &lt;a href="https://github.com/arrow-kt/arrow" rel="noopener noreferrer"&gt;Arrow&lt;/a&gt;, &lt;a href="https://github.com/kosi-libs/Kodein" rel="noopener noreferrer"&gt;Kodein&lt;/a&gt; or even &lt;a href="https://www.http4k.org/" rel="noopener noreferrer"&gt;http4K&lt;/a&gt;, it was nice to have strong players present as well and see industry support for the ecosystem.  It is obviously a big PR and recruitment move for them, but hey I find it smart and it's a strong win/win situation in my opinion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcld7t5k6q311w8yvpetg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcld7t5k6q311w8yvpetg.png" alt="My KotlinConf experience" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Adyen booth at KotlinConf. Adyen uses Kotlin a lot for the payment terminals&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The sessions
&lt;/h2&gt;

&lt;p&gt;I spent most of my time roaming around talking to people, so I won't describe all of the sessions I joined but instead I want to give some items which I really liked or who caught my attention particularly.&lt;/p&gt;

&lt;p&gt;First, I wanna start with the one session which was an absolute blast for me : &lt;a href="https://kotlinconf.com/speakers/68b14adf-80d2-4a38-b6c9-d8c66b343dca/#Video%20Game%20Hacking%20using%20Kotlin/Native" rel="noopener noreferrer"&gt;Video Game Hacking using Kotlin/Native&lt;/a&gt; by Ignat Beresnev. It was fun, entertaining, I've learnt stuff, delivery was amazing and the topic was great as well. Surprisingly, it packed a whole lot of punch for a lightning talk. Honestly though, what's more fun that learning about memory hacking and &lt;strong&gt;seeing cars appear straight in GTA, all of that in Kotlin&lt;/strong&gt;. The presentation is full of well timed jokes, and honestly it grew me as a speaker. I can't recommend it enough.&lt;/p&gt;

&lt;p&gt;Then, there was &lt;a href="https://kotlinconf.com/speakers/a4d26fc1-9707-4c86-bc82-70b7ce05823c/#Crash%20Course%20on%20the%20Kotlin%20Compiler" rel="noopener noreferrer"&gt;the crash course on compilers by my colleague Amanda&lt;/a&gt;. Damn, it's impressive to see deep dive talks from people who are really passionate about their craft. Imposter syndrome hit hard there, and the room was absolutely packed! I have to admit that the topic was quite far from my usual dabblings and I'm still unsure what I could build a compiler plugin for ^^, but I've definitely learnt a lot about how Kotlin works under the hood!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14l2dbmntybewsfh93xx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14l2dbmntybewsfh93xx.png" alt="My KotlinConf experience" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Crash course on compilers, in a dope room, fully packed&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Compose, compose and compose
&lt;/h2&gt;

&lt;p&gt;You probably know it, I'm a backend guy. I make do with frontend if I have to, mostly using &lt;a href="https://lit.dev/" rel="noopener noreferrer"&gt;Web Components&lt;/a&gt;. I tried &lt;a href="https://developer.android.com/jetpack/compose" rel="noopener noreferrer"&gt;Compose&lt;/a&gt; back in the past, mostly for web but I really never got excited about it. I mean, I really do like the components idea and the language is a blast but maybe it was too early. Things weren't documented as much as I wanted to, reloading was slow, tooling wasn't quite there yet, ... I really wasn't reaching the experience that I wanted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I have to say however that they did manage to hype me up for it again during the conference.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, Sebastian Aigner adnd Nikita Lipsky announcing &lt;a href="https://kotlinconf.com/speakers/a82c6942-6e2b-4d80-9883-0b84b932d6ef/#Compose%20Multiplatform%20on%20iOS" rel="noopener noreferrer"&gt;Compose Multiplatform on iOS&lt;/a&gt; with a banging demo and a flawless (and fun) distribution.&lt;br&gt;&lt;br&gt;
Then John O'Reilly and Martin Bonnin &lt;a href="https://kotlinconf.com/speakers/0392772c-28d4-47f6-bd39-47d743fb4a81/#Confetti:%20building%20a%20Kotlin%20Multiplatform%20conference%20app%20in%2040min" rel="noopener noreferrer"&gt;building a Kotlin Multiplatform conference app&lt;/a&gt; in 40min live on stage. I mean, of course those speakers rock, they're seasoned and it's clear they're having a blast on stage. But &lt;strong&gt;it's also hard not to get seduced seeing so much code reuse between the front and the backend of an app and seeing it run both on android and iOS in minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I also went to see &lt;a href="https://kotlinconf.com/speakers/e21ee3c3-2e72-4fd2-8e5c-30511e30fe66/#You%20can%20do%20desktop%20too!" rel="noopener noreferrer"&gt;Victor Kropp talk about how they used compose to build the Jetbrains toolbox&lt;/a&gt; (use it if you don't yet, it's great!). The toolbox is one application that I quite admire, it's lean and mean while still getting the job done. It's always there for you and damn, it's pretty. So when I learnt it was built using Jetpack Compose for Desktop, my interest for compose obviously grew even more.&lt;/p&gt;

&lt;p&gt;Finally, adding the Compose for web demos (using WASM) constantly running on the Jetbrains booth, it's hard not to get a little excited about the future for Kotlin.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;I've been into the industry for a little while and one learns to be pessimistic and cautious over time, but has the time finally coming where I can use one language (that I love, sorry Javascript) across all my stacks?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How much compose is too much though?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;One thing however bothered me a little bit during the conference, is the amount of Kotlin Multiplatform / Android relative to the server content that was present there.&lt;/strong&gt; In terms of content, sessions, but also sponsors and visitors. I might be biased really, because that's something that I seem to see in the ecosystem already. Let's have a quick look at it.&lt;/p&gt;

&lt;p&gt;The numbers I'm about to give are not precise, because the frontier between Multiplatform and backend is not always crystal clear, but if we look at the ~80 talks present at the conference, I counted about 20 compose related talked, and 30 backend related ones. The rest is either compiler, platform, community, ... related.&lt;br&gt;&lt;br&gt;
It's honestly better than I was expecting, much likely because I used the conference as an opportunity to learn more about Compose.&lt;br&gt;&lt;br&gt;
Out of those 30 backend talks though, more than 12 of those are coroutines related one way or another. That's massive and there's clearly something to see here ^^.&lt;/p&gt;

&lt;p&gt;If we look at the Kotlin Foundation partners, the Kotlin partners are Jetbrains, Google, and the newly announced ones are Gradle, Shopify and Touchlab.&lt;br&gt;&lt;br&gt;
Of course, all of those are invested in backend as well with Kotlin being the default for backend at Google those days for example. But even keeping Jetbrains apart (even though it obviously is MASSIVELY invested in KMM), touchlabs and shopify are heavily weighting in the KMM ecosystem. And Google is obviously behind most of the Jetpack Compose and Android technologies. That leaves us with Gradle, which arguably is mostly involved in tooling.&lt;br&gt;&lt;br&gt;
It might be too black and white statement, but as far as I see today most of the partners are more influential in the KMM part of the ecosystem.&lt;/p&gt;

&lt;p&gt;Obviously, as I mentioned this is a bet that I'd love to see succeed. &lt;strong&gt;The more successful KMM is, the better the whole ecosystem will become&lt;/strong&gt;. At the same time, &lt;strong&gt;I can't stop thinking that there is SO MUCH backend Java going on out there, it feels a little like a missed opportunity&lt;/strong&gt;. Java is also has been getting a fresh look lately with the faster release cadence and the accrued amount of DevRel going on around it. It looks to me like backend folks aren't being seduced by the language as much as they could be.&lt;/p&gt;

&lt;p&gt;In any case, KotlinConf was an amazing experience for me, it was really great to be around so many folks in the ecosystem and seeing how vibrant it becomes over time. And a big thanks to Jetbrains for all of the hard work. Definitely count me in for next time :).&lt;/p&gt;

&lt;p&gt;See ya!&lt;br&gt;&lt;br&gt;
Julien&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>conference</category>
      <category>devrel</category>
      <category>android</category>
    </item>
    <item>
      <title>Replacing Postman with the Jetbrains HTTP Client</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Thu, 02 Feb 2023 10:17:10 +0000</pubDate>
      <link>https://dev.to/jlengrand/replacing-postman-with-the-jetbrains-http-client-gpa</link>
      <guid>https://dev.to/jlengrand/replacing-postman-with-the-jetbrains-http-client-gpa</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0plen08du4ybfj9wqe3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0plen08du4ybfj9wqe3t.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="906"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR : I've created the first version of an openapi-generator for the jetbrains HTTP Client, and together with the CLI runner it allows you to play against APIs without ever going out of your terminal and it can even run in your CI/CD pipeline. &lt;a href="https://github.com/jlengrand/dotaClient" rel="noopener noreferrer"&gt;See repository here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I work a lot with APIs, whether for an app I develop myself, or to interact with others. Most people I know tend to use Postman for this. But &lt;a href="https://twitter.com/jlengrand/status/1620343955127689216" rel="noopener noreferrer"&gt;I don't quite like the product any more&lt;/a&gt;, and it forces me to move out of my IntelliJ environment which really kills my productivity.&lt;/p&gt;

&lt;p&gt;With a combination of the &lt;a href="https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html" rel="noopener noreferrer"&gt;Jetbrains HTTP Client&lt;/a&gt; and &lt;a href="https://openapi-generator.tech/" rel="noopener noreferrer"&gt;OpenAPI generators&lt;/a&gt; we can do much better, in a semi automated way and even reuse our code on our CI/CD. Let's dive into how!&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick introduction to the Jetbrains HTTP Client
&lt;/h2&gt;

&lt;p&gt;A lot of people don't know about the &lt;a href="https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html" rel="noopener noreferrer"&gt;Jetbrains HTTP Client&lt;/a&gt;, which is a Jetbrains proprietary text file format that allows you to run API requests easily.&lt;/p&gt;

&lt;p&gt;One thing I love about it is that it's integrated into the IDE, and it can generate those requests for you. For example, looking at this &lt;a href="https://github.com/spring-guides/tut-spring-boot-kotlin" rel="noopener noreferrer"&gt;Spring Boot Kotlin Sample&lt;/a&gt;, next to each &lt;code&gt;Mapping&lt;/code&gt; there is a "Open in HTTP Client" option in the gutter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fgnkbuynz469gt67sli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fgnkbuynz469gt67sli.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="225"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The "Open in HTTP Client" option in the gutter of our IDE&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When clicking it, it will generate a scratch file for this request :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6p2e7kd41be78ahrqmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh6p2e7kd41be78ahrqmc.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="183"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The generated request&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can do many things with those requests, like setting Content-Type, sending body payload and more but&lt;a href="https://www.jetbrains.com/help/idea/exploring-http-syntax.html" rel="noopener noreferrer"&gt;I'll direct you to the documentation&lt;/a&gt; for more.&lt;/p&gt;

&lt;p&gt;The one thing that I want to show you here is that it defines variables for you that you can fill in using a separate configuration file. Pick an environment, and fill in a value for the variables you need. You can either create a public file, or a private one for, say, API Keys.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfwaqbomoypk1kf8ow3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfwaqbomoypk1kf8ow3y.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="274"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Me creating a Public Environment file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Running the request gets me the response, as expected, and it's even saved in a log file for me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdjgmulx1t2yoly9x1mr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpdjgmulx1t2yoly9x1mr.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="866"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Running the request with the slug set to ispum returns a valid response&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The best thing about all this is that everything is a text file, so I can put my request file with its config in a folder in my repository, pushes it to my repository together with my source files and I suddenly have an easy way to interact with my API.&lt;/p&gt;

&lt;p&gt;I hope that by this time you're convinced of the value of the Client 😊. But wait, we can do much more!&lt;/p&gt;
&lt;h2&gt;
  
  
  Generating full API Clients
&lt;/h2&gt;

&lt;p&gt;Many of you are probably aware of the OpenAPI specification (formerly swagger files). It is basically a standard to describe your API. If you write a spec file for it, you basically write a "contract" for how your API works. The nice thing about it is that you can then use this contract to automagically generate a lot of things for your API! That can be clients, in the language of your choice, mock servers, or even documentation. There is a great project for this, conveniently called &lt;a href="https://openapi-generator.tech/" rel="noopener noreferrer"&gt;OpenAPI Generator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;During the Christmas holidays, I set myself on a quest to create a simple generator for the &lt;a href="https://dev.to/jlengrand/getting-started-with-openapi-generators-tips-and-tricks-1ckc-temp-slug-8061458"&gt;Jetbrains HTTP Client&lt;/a&gt;, which didn't exist yet. As of yesterday, it's released! Let's look through it with an example!&lt;/p&gt;

&lt;p&gt;I'm a big DOTA 2 player, and let's imagine I want to start playing with the &lt;a href="https://docs.opendota.com/" rel="noopener noreferrer"&gt;Open DOTA API&lt;/a&gt;. Conveniently, it provides us with an &lt;a href="https://api.opendota.com/api" rel="noopener noreferrer"&gt;exhaustive OpenAPI specification file&lt;/a&gt;. Let's have a quick look&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zcwor1rs2lghdw5hqm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zcwor1rs2lghdw5hqm6.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The beginning of the OpenAPI Spec file&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As we can see, it describes the location of the API, as well as what kind of HTTP Calls you can make with the associated parameters.&lt;/p&gt;

&lt;p&gt;Let's generate a full HTTP Client for it. First, we install the OpenAPI Generator&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ brew install openapi-generator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we generate the client in a new folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ openapi-generator generate -i https://api.opendota.com/api -g jetbrains-http-client -o dotaClient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;✨That's it!✨&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When we open the folder in IntelliJ, we're presented with a few very nice things (&lt;a href="https://github.com/jlengrand/dotaClient" rel="noopener noreferrer"&gt;Here is the GitHub repo if you want to see it for yourself&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First, a complete documentation with all available endpoints and how they work in the README. Each call is clickable and link to locations in our source code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqne89wtlgwil57riulv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqne89wtlgwil57riulv.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="663"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A screenshot of the Dota Client README&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Then, an Apis folder with all available calls and their related documentation. Here is the generated code for the &lt;code&gt;Heroes&lt;/code&gt; endpoints :
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## HeroesApi

### GET /heroes
# @name heroesGet
GET http://api.opendota.com/api/heroes

### GET /heroes/{hero_id}/durations
# @name heroesHeroIdDurationsGet
GET http://api.opendota.com/api/heroes/{{hero_id}}/durations

### GET /heroes/{hero_id}/itemPopularity
# @name heroesHeroIdItemPopularityGet
GET http://api.opendota.com/api/heroes/{{hero_id}}/itemPopularity

### GET /heroes/{hero_id}/matches
# @name heroesHeroIdMatchesGet
GET http://api.opendota.com/api/heroes/{{hero_id}}/matches

### GET /heroes/{hero_id}/matchups
# @name heroesHeroIdMatchupsGet
GET http://api.opendota.com/api/heroes/{{hero_id}}/matchups

### GET /heroes/{hero_id}/players
# @name heroesHeroIdPlayersGet
GET http://api.opendota.com/api/heroes/{{hero_id}}/players

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

&lt;/div&gt;


&lt;p&gt;Now let's create en environment file and run all those calls for a given hero. Let's use the &lt;code&gt;id&lt;/code&gt; of my favourite hero : &lt;code&gt;Crystal Maiden&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "dev": {
    "hero_id": "5"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like this, I can start running queries against the API, and for example see which players have the most wins with Crytal Maiden :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylflbat59woe2sosbyxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylflbat59woe2sosbyxc.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="250"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A screenshot of the results of running the Heroes to Player endpoint&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I can also decide to run the Health endpoint and see whether the DOTA or Steam servers are down 😊&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4w4de1exsq5vqdd6eoj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4w4de1exsq5vqdd6eoj.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="258"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Results of running a query against the Health endpoint&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Objective reached! I can play against external APIs locally, in source code, without having to use any tool like Postman and without going out of IntelliJ.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding tests
&lt;/h2&gt;

&lt;p&gt;Running endpoints is nice, but we also want to be able to make sure expectations are fulfilled! Thankfully, there is a way to do this the the HTTP Request client :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### GET /heroes
# @name heroesGet
GET http://api.opendota.com/api/heroes

&amp;gt; {%
    client.test("Request executed successfully", function() {
        // client.assert(response.status === 200, "Response status is not 200");
        client.assert(response.body.length == 126, "DOTA 2 currently has 123 heroes");
    });
%}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Creating a purposefully failing test&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This should fail, as DOTA 2 currently has 123 heroes :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdmpg71j2rhcbzta04u0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkdmpg71j2rhcbzta04u0.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="122"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Bingo!&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Going even further
&lt;/h2&gt;

&lt;p&gt;I've always been a bit frustrated at Jetbrains for their HTTP Client, because it was completely bound to Intellij. As amazing as it is, I can't spend a lot of time crafting nice custom API calls if I still have to create tests for my CI anyways. At the end of the day, it's double the work for me.&lt;/p&gt;

&lt;p&gt;But last month, they announced something that filled me with joy  : &lt;a href="https://blog.jetbrains.com/idea/2022/12/http-client-cli-run-requests-and-tests-on-ci/" rel="noopener noreferrer"&gt;A CI runner for the HTTP Request files&lt;/a&gt;! &lt;strong&gt;All of a sudden, all the work we did above became 1000x more useful!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's have a look into it :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We download the (preview, for now), tool and unzip it:
&amp;lt;!--kg-card-begin: markdown--&amp;gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -f -L -o ijhttp.zip "https://jb.gg/ijhttp/latest
$ unzip ijhttp.zip

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

&lt;/div&gt;




&lt;ul&gt;
&lt;li&gt;Finally, we run it against the files we have generated above, with our environment file:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./ijhttp/ijhttp Apis/HeroesApi.http --env-file Apis/http-client.env.json --env dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Et voilà!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$./ijhttp/ijhttp Apis/HeroesApi.http --env-file Apis/http-client.env.json --env dev  ✔ ╱ 6s  ╱ 10:23:37 
┌─────────────────────────────────────────────────────────────────────────────┐
│ Running IntelliJ HTTP Client with │
├──────────────────────┬──────────────────────────────────────────────────────┤
│ Files │ HeroesApi.http │
├──────────────────────┼──────────────────────────────────────────────────────┤
│ Public Environment │ hero_id = 5 │
├──────────────────────┼──────────────────────────────────────────────────────┤
│ Private Environment │ │
└──────────────────────┴──────────────────────────────────────────────────────┘
Request 'heroesGet' GET http://api.opendota.com/api/heroes
Failed HeroesApi.http#0 Request executed successfully
Request 'heroesHeroIdDurationsGet' GET http://api.opendota.com/api/heroes/5/durations
Request 'heroesHeroIdItemPopularityGet' GET http://api.opendota.com/api/heroes/5/itemPopularity
Request 'heroesHeroIdMatchesGet' GET http://api.opendota.com/api/heroes/5/matches
Request 'heroesHeroIdMatchupsGet' GET http://api.opendota.com/api/heroes/5/matchups
Request 'heroesHeroIdPlayersGet' GET http://api.opendota.com/api/heroes/5/players

6 requests completed, 1 have failed tests
RUN FAILED

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

&lt;/div&gt;



&lt;p&gt;We still have our failing test, which will fail our CI. Task failed successfully!&lt;/p&gt;

&lt;p&gt;The last step to have a complete setup is to setup a CI action. Let's use GitHub actions for this, fix our failing test, and see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Run API tests based on Jetbrains HTTP Client

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: Downloads and runs the Jetbrains HTTP Client CLI
        run: |
          curl -f -L -o ijhttp.zip "https://jb.gg/ijhttp/latest"
          unzip ijhttp.zip
          ./ijhttp/ijhttp Apis/HeroesApi.http --env-file Apis/http-client.env.json --env dev

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

&lt;/div&gt;



&lt;p&gt;We checkout, set the Java version to 17 and run the &lt;code&gt;ijhttp&lt;/code&gt; package like we did locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jlengrand/dotaClient/actions/runs/4073382476" rel="noopener noreferrer"&gt;Success&lt;/a&gt;! We now have reliable CI tests using Jetbrains HTTP Client requests!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczoobkh4jp9ur3n2t03y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczoobkh4jp9ur3n2t03y.png" alt="Replacing Postman with the Jetbrains HTTP Client" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;There is so much more I could talk about, like the fact that the HTTP Client supports other protocols like GraphQL, handles redirections and more, but I'll keep it to this for now.&lt;/p&gt;

&lt;p&gt;Do keep in mind that the &lt;a href="https://github.com/OpenAPITools/openapi-generator" rel="noopener noreferrer"&gt;Jetbrains HTTP Client generator&lt;/a&gt; is still in a very early phase at the moment. For example, I have no support for authentication or most headers at the moment. It won't break anything, but you might have to add things manually to the generated files.&lt;/p&gt;

&lt;p&gt;But hey, I do welcome requests and contributions!&lt;/p&gt;

&lt;p&gt;Hope you found the read useful. Feel free to ping me on &lt;a href="https://mastodon.online/@jlengrand" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; or &lt;a href="//twitter.com/jlengrand/"&gt;Twitter&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>api</category>
      <category>openapi</category>
      <category>kotlin</category>
      <category>ci</category>
    </item>
    <item>
      <title>2022 retro, and what's up</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Wed, 11 Jan 2023 10:07:52 +0000</pubDate>
      <link>https://dev.to/jlengrand/2022-retro-and-whats-up-47ej</link>
      <guid>https://dev.to/jlengrand/2022-retro-and-whats-up-47ej</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvr7ddlyh5fjiopoenqo9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvr7ddlyh5fjiopoenqo9.jpg" alt="2022 retro, and what's up" width="800" height="1066"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like for many other people, the end of the year is a good time to stop for a second and look back at what one has accomplished. I'm notoriously bad at celebrating my successes, always instead looking at the next thing. So please bear with me for this one time humblebrag post.&lt;/p&gt;

&lt;p&gt;So let's dive in, here's a dive into what's happened for me last year, professionally and at a more personal level (which, honestly I'm even more proud of :)).&lt;/p&gt;

&lt;h2&gt;
  
  
  At work
&lt;/h2&gt;

&lt;h3&gt;
  
  
  My debuts into DevRel
&lt;/h3&gt;

&lt;p&gt;2022 was my first professional year in the realm of Developer Relations! I can't believe it took so long for me to find what many would call &lt;a href="https://en.wikipedia.org/wiki/Ikigai" rel="noopener noreferrer"&gt;Ikigai&lt;/a&gt;. Something I love, I'm good at, and I can be paid for!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4dt9ykxshyvrjo9gl44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4dt9ykxshyvrjo9gl44.png" alt="2022 retro, and what's up" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;An illustration of Ikigai (&lt;a href="https://www.calmsage.com/10-rules-of-ikigai/" rel="noopener noreferrer"&gt;https://www.calmsage.com/10-rules-of-ikigai/&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I absolutely love my job, and even though it's clear I can do much much better, the world around me seems to agree with the fact that I'm doing OK at it. I think it's genuinely the first time in my professional career where I feel like I exactly belong in the environment I evolve in, and I make a direct impact.&lt;/p&gt;

&lt;p&gt;So there's that!&lt;/p&gt;

&lt;h3&gt;
  
  
  From entity, to team
&lt;/h3&gt;

&lt;p&gt;Last year ended in a rocky way, with my manager (and mentor) living the company and Adyen deciding to change it's strategy. It took me (and us) a while to figure out what we were going to be doing. After all, Adyen being a closed product, what value can Developer Relations bring ? Turns out, a lot!&lt;/p&gt;

&lt;p&gt;A year later, we now officially are our own team, with a clear strategy. I have the chance and honor to lead 3 motivated folks with a different set of strenghts, and it really feels like we can move mountains together. And it's so much fun as well, I'm super proud of them, and what we've done together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving the needle
&lt;/h3&gt;

&lt;p&gt;Talking about this, our team helped create a lot of brand awareness for Adyen. Collectively, we talked to over 6 thousand (!!) people, gave over 30 conference talks and in total reached over 150k folks. It's mad to think about it, especially for a team team where everyone is basically starting up in DevRel 😊.&lt;/p&gt;

&lt;p&gt;But even more important, we also helped a lot bring awareness about Developer Experience inside the company, so much so that it's becoming one of the priorities for 2023. It's a great achievement, because it'll improve even further our focus for our merchants, and bring many internal teams together. We're definitely looking forward 2023.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnswwg5ypzza2ejt1v2vb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnswwg5ypzza2ejt1v2vb.png" alt="2022 retro, and what's up" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Apoorva and I speaking about Developer Experience at the internal Adyen conference&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Meetups, and Conferences
&lt;/h3&gt;

&lt;p&gt;I've been quite present on the conference scene this year, even though it's definitely not one of my main objectives. It brings me crazy amounts of energy to meet folks, share knowledge and to get people excited. I'm proud of the fact that I've started speaking of things that are much closer to my heart as well (sustainability). It's also the first year that people actually started contacting me to speak at conferences, and that honestly feels amazing. This year is also the very first time I was invited &lt;a href="https://open.spotify.com/show/6rxip23OishKIhUxtAdaw7?si=t4QPeEk_R6mAu_IKpI0V7w&amp;amp;nd=1" rel="noopener noreferrer"&gt;on a podcast&lt;/a&gt;, to talk sustainability and DevRel, and it felt great!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftx5l046t9kbajdrpcml6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftx5l046t9kbajdrpcml6.png" alt="2022 retro, and what's up" width="800" height="266"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A photo of me speaking about the Climate Emergency during the DevFest Lille. The Q/A turned into a discussion for almost 30 minutes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This year, I've also helped reboot &lt;a href="https://www.meetup.com/amsterdam-devrel-salon/" rel="noopener noreferrer"&gt;the Amsterdam DevRel salon&lt;/a&gt; and learnt a lot from the folks there. It's definitely different to organize a Meetup for people who's job it is to speak 😊. Gaining a lot of value from it, and there's demand for it. Our first 2023 session expect almost 50 people!&lt;/p&gt;

&lt;p&gt;Oh, and thanks to &lt;a href="https://mastodon.online/@floord@mastodon.lol" rel="noopener noreferrer"&gt;Floor&lt;/a&gt; for helping organize!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw9b5fxrzhii6d97r904r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw9b5fxrzhii6d97r904r.png" alt="2022 retro, and what's up" width="800" height="1074"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A photo of Floor and me at the Halloween edition of the DevRel Salon. We respectively wear an egg, and a lion costume.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Industry recognition
&lt;/h3&gt;

&lt;p&gt;I love spending time create awareness for tools I love. And if you know me, you definitely are aware I'm in love with &lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt; and &lt;a href="https://www.gitpod.io/" rel="noopener noreferrer"&gt;Gitpod&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Even though it's definitely not a main goal for me, it feels seeing recognition for my efforts. In 2022, I became a &lt;a href="https://developers.google.com/community/experts/directory/profile/profile-julien-lengrand-lambert" rel="noopener noreferrer"&gt;Kotlin Google Developer Expert&lt;/a&gt; as well as a &lt;a href="https://www.gitpod.io/community/heroes" rel="noopener noreferrer"&gt;Gitpod Hero&lt;/a&gt;. There's some nice perks to it, but being closer to the community gives me so much energy every day, I'm super grateful about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blogging and Writing
&lt;/h3&gt;

&lt;p&gt;Even though I definitely didn't spend as much time as I wanted to writing this year, I still managed to write a few pieces on this blog and in written magazines. Again, the pleasure I take writing is nothing compared to the joy of hearing that it helped someone or getting feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdi0lo0p2npxxsqw8xyjc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdi0lo0p2npxxsqw8xyjc.png" alt="2022 retro, and what's up" width="800" height="1066"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;My face in a magazine, talking about things that are close to my heart (Gitpod)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  From speaker, to reviewer
&lt;/h3&gt;

&lt;p&gt;It's been a couple years already that I help the Dutch Java community by being one of the editors of the Dutch Java Magazine. I also absolutely love JFall above all other conferences, and try to always have a speaking slot there.&lt;/p&gt;

&lt;p&gt;This year, I had the honor to be part of the selection committee as well for the talks. It was great having the opportunity helping shape this conference that I love so much. That, and helping first time speakers prepare for their talk as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  And at home
&lt;/h2&gt;

&lt;p&gt;As I was mentioning, even though I'm quite happy with how my work life turned out last year, I'm even prouder of everything we achieved at home.&lt;/p&gt;

&lt;h3&gt;
  
  
  No compromises
&lt;/h3&gt;

&lt;p&gt;It's been a few years already that we're conscious about out footprint on the planet, and we've decided to drasctically reduce our impact anywhere we could. And one of the major items there is flying. As of 2018, plane travel totalled almost 40% of my CO2 footprint. I decided to stop flying, unless really necessary.&lt;/p&gt;

&lt;p&gt;Combining this with being a Developer Advocate is not always simple. After all, a big part of our job is to be at conferences, close to our audience. Well I managed to be present at over 15 conferences this year, without ever boarding a plane. I've also done absolutely no compromise on the topics I wanted to present.&lt;/p&gt;

&lt;p&gt;Turns out, you can go far with the train. I've even managed to go all the way to Barcelona 😊. I'm happy to see it's possible, and glad Adyen was nice enough to give me the extra bandwidth necessary for this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Producing a good chunk of our food
&lt;/h3&gt;

&lt;p&gt;It's been a couple years we try to buy less, and produce some of what we consume at home. It started with many home products (washing powder, dishwasher tablets, detergent, ...) and last year we started the gardening experiment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ji97ld7un8nlmoaxcd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi6ji97ld7un8nlmoaxcd.png" alt="2022 retro, and what's up" width="800" height="601"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The family together on the garden, enjoying our very first red cabbage&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We went bigger this year, with a 200m2 garden! It was quite the task, actually much more than we expected. Especially with close to 0 previous experience ^^. Almost 60% of everything we planted died 😅.&lt;/p&gt;

&lt;p&gt;But even then, we managed to produce over 160 kg of food this year! This is by far my proudest achievement with the family. We are, and have been quite largely feeding ourselves with our own produce. We made cans, froze a lot, and it's such a joy to create meals that come 100% from stuff you grew yourself! We're definitely planning on doubling down on this next year.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwft28bg8zvr094tbkt7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwft28bg8zvr094tbkt7.png" alt="2022 retro, and what's up" width="800" height="1068"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;That's what 30Kg of potatoes look like!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Gardening also made us so much more aware of the climate emergency. When you're in the field, you realize how f*ed and unreliable the weather is becoming. I'm glad about it, because it's something we can't ignore anymore and we can bring our kids on the journey. Acting is definitely better than plain despair.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzobabw9lq0agr4b362gc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzobabw9lq0agr4b362gc.png" alt="2022 retro, and what's up" width="800" height="1063"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;This is what the garden looked like back in July when it stopped raining for almost 90 days in a row. We live in the Netherlands!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So what's with 2023?
&lt;/h2&gt;

&lt;p&gt;It's hard to reflect back without ending with a glimpse at what's coming. This year will be slightly different than last year, but even more exciting.&lt;/p&gt;

&lt;p&gt;I'll most likely be less present at conferences, as we will be focusing a lot more on content creation with the rest of the team. This means that I'll get my hands much more dirty and that's great! I actually already managed to do a minor &lt;a href="https://dev.to/jlengrand/a-tale-of-fixing-a-tiny-openapi-bug-530h-temp-slug-4743026"&gt;Open-Source contribution&lt;/a&gt; to OpenAPI early this year so that's a great start.&lt;/p&gt;

&lt;p&gt;So, more content. I also will dive much deeper into Developer Experience, and especially how to measure it. Developer Advocacy definitely ties into Developer Experience, being on the forefront of the product and I want to learn better to convince stakeholders by leveraging numbers. Finally, I will learn the business side of software. This is one of the parts I have been missing in the past years, and it's a gap I want to fill. I want to be generating revenue myself this year, by learning to recognize gaps in product offerings.&lt;/p&gt;

&lt;p&gt;So let's get to it! So what are your plans for 2023?! I'm mostly available on &lt;a href="https://mastodon.online/@jlengrand" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/julienlengrand/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; those days 😊, though you can still find me on &lt;a href="https://twitter.com/jlengrand" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;...&lt;/p&gt;

</description>
      <category>community</category>
      <category>development</category>
      <category>self</category>
      <category>reflections</category>
    </item>
    <item>
      <title>A tale of fixing a tiny OpenAPI bug</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Sat, 31 Dec 2022 15:35:19 +0000</pubDate>
      <link>https://dev.to/jlengrand/a-tale-of-fixing-a-tiny-openapi-bug-3h3o</link>
      <guid>https://dev.to/jlengrand/a-tale-of-fixing-a-tiny-openapi-bug-3h3o</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foetx8y9joswqvtfehfrf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foetx8y9joswqvtfehfrf.png" alt="A tale of fixing a tiny OpenAPI bug" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR : I found and fixed a tiny bug in OpenAPI, learnt about the library, &lt;a href="https://github.com/FasterXML/jackson" rel="noopener noreferrer"&gt;jackson&lt;/a&gt; and git in the process.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yesterday, I was sharing some tips on how to get started with OpenAPI generators. Turns out, within minutes of me starting to use the library, I found a bug that was causing heap space exceptions. This blog is about what I learnt while fixing it 😊.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facing the bug
&lt;/h2&gt;

&lt;p&gt;The first thing I started doing while testing the application, before even creating my own generator was running a few existing generators for languages I know. I started with Kotlin, and added the debug flags to it to see what kind of objects were generated by the OpenAPI parser.&lt;/p&gt;

&lt;p&gt;Within minutes, I was running the Kotlin (of course) generator, with the &lt;code&gt;debugSupportingFiles&lt;/code&gt; global property activated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevt3nxasq07isqy917mo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevt3nxasq07isqy917mo.png" alt="A tale of fixing a tiny OpenAPI bug" width="800" height="549"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;IntelliJ debug configuration with the debugSupportingFiles global property&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After a few seconds, the process crashed with a Java heap space exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[main] INFO o.o.codegen.TemplateManager - writing file /Users/julienlengrand-lambert/Developer/openapi-generator/samples/client/petstore/kotlin/http/client/docs/UserApi.md
[main] INFO o.o.codegen.DefaultGenerator - ############ Supporting file info ############
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.fasterxml.jackson.core.util.TextBuffer.carr(TextBuffer.java:967)
    at com.fasterxml.jackson.core.util.TextBuffer.expand(TextBuffer.java:928)
    at com.fasterxml.jackson.core.util.TextBuffer.append(TextBuffer.java:682)
    at com.fasterxml.jackson.core.io.SegmentedStringWriter.write(SegmentedStringWriter.java:58)
    at com.fasterxml.jackson.core.json.WriterBasedJsonGenerator.writeRaw(WriterBasedJsonGenerator.java:605)
    at com.fasterxml.jackson.core.util.DefaultIndenter.writeIndentation(DefaultIndenter.java:94)
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not knowing at all what I was doing, I asked for directions &lt;a href="https://openapi-generator.slack.com/archives/CLSB0U0R5/p1669735856969609" rel="noopener noreferrer"&gt;on the Slack channel&lt;/a&gt; of the project and got suggestions within minutes with ideas of things to try out.&lt;/p&gt;

&lt;p&gt;For me, the main question was : &lt;strong&gt;Is it only happening on my machine 😬?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Justin was nice enough to &lt;a href="https://github.com/OpenAPITools/openapi-generator/issues/14161" rel="noopener noreferrer"&gt;file a bug for me&lt;/a&gt;, and I started diving into the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the culprit
&lt;/h2&gt;

&lt;p&gt;Turns out, the issue &lt;em&gt;only&lt;/em&gt; happens in debug mode, with that specific flag set. It was also happening for all of the generators I tried, and all the specification files. Extending the heap size to humongous values wasn't helping. It had to come from the code somehow.&lt;/p&gt;

&lt;p&gt;After a little while, I could track down &lt;a href="https://github.com/OpenAPITools/openapi-generator/blob/7c587ce061f1b2ef6bcd3fc406224452f79099a9/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java#L831" rel="noopener noreferrer"&gt;the line&lt;/a&gt; that was creating the crash. It was all happening in the &lt;code&gt;DefaultGenerator&lt;/code&gt; (which all generators extend from), and only if the debug flag was set. Makes sense.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (GlobalSettings.getProperty("debugSupportingFiles") != null) {
    LOGGER.info("############ Supporting file info ############");
    Json.prettyPrint(bundle); // BIG BOUM, BIG BADABOUM 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jason, again, very helpful on Slack, mentioned that the objects generated by the generators can contain infinite loops. Those loops, when trying to transform them into a JSON file would crash the JVM. That's also what was showing in the stack trace of the exception.&lt;/p&gt;

&lt;p&gt;It was time to track down when the bug was introduced!&lt;/p&gt;

&lt;h2&gt;
  
  
  Git bisect to the rescue!
&lt;/h2&gt;

&lt;p&gt;I won't go deep into how &lt;code&gt;git bisect&lt;/code&gt; works. You can &lt;a href="https://www.git-tower.com/learn/git/faq/git-bisect/" rel="noopener noreferrer"&gt;read about it yourself&lt;/a&gt;. The basic idea is that you provide git with a valid commit (before the bug was there) and a faulty commit (with the bug). You'll also need a quick way to see if the bug is present (in my case, running the run configuration and seeing the heap space crash).&lt;/p&gt;

&lt;p&gt;Git will iteratively select commits and ask you to tell him whether the bug is there, until you know precisely the ONE COMMIT which introduced the problem.&lt;/p&gt;

&lt;p&gt;It basically looked like this :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ git checkout master # clean state
$ git bisect start
$ git bisect bad HEAD # bug in there
$ git bisect good v5.0.0 # no bug
$ git bisect good # no crash
$ git bisect bad # crash
$ git bisect good
$ git bisect bad
$ git bisect good
$ git bisect bad
$ git bisect good
$ git bisect end
$ git bisect reset # back to work, let's check the bug now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honestly, I never had to use it in the past in my professional life and I feel blessed to have an extra tool in my box now 😊. The whole thing was sorted in less than 5 minutes 😅.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the bug
&lt;/h2&gt;

&lt;p&gt;Once we know the commit which introduced the issue, it's easy to track down where exactly the problem is. Look, &lt;a href="https://github.com/OpenAPITools/openapi-generator/blob/ef8e55ca21d71dd1e68c47ea239974c75b96a4f8/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java#LL200-L215C6" rel="noopener noreferrer"&gt;it's right there&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    /**
     * @return contentTypeToOperation
     * returns a map where the key is the request body content type and the value is the current CodegenOperation
     * this is needed by templates when a different signature is needed for each request body content type
     */
    public Map&amp;lt;String, CodegenOperation&amp;gt; getContentTypeToOperation() {
        LinkedHashMap&amp;lt;String, CodegenOperation&amp;gt; contentTypeToOperation = new LinkedHashMap&amp;lt;&amp;gt;();
        if (bodyParam == null) {
            return null;
        }
        LinkedHashMap&amp;lt;String, CodegenMediaType&amp;gt; content = bodyParam.getContent();
        for (String contentType: content.keySet()) {
            contentTypeToOperation.put(contentType, this);
        }
        return contentTypeToOperation;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The method which introduced the bug creating those heap space exceptions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you understand what the method does, it's quite clear why the heap explodes. This method is basically adding to an object, the object itself, but with a key of a certain type of format. No wonder it generates infinite loops when the JSON parser runs through it 😊.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmw4cznxwiqhmdbxf1gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbmw4cznxwiqhmdbxf1gq.png" alt="A tale of fixing a tiny OpenAPI bug" width="800" height="99"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A look inside the object in debug mode at runtime&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, the actual problem is not that method itself. The real issue is that it's not called anywhere 😅. It &lt;em&gt;shouldn't&lt;/em&gt; be run at all. Actually, there's literally 0 direct usages of the method in the whole library (even in the &lt;code&gt;toString&lt;/code&gt; method)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fha4643ohzpgpwu2f7uea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fha4643ohzpgpwu2f7uea.png" alt="A tale of fixing a tiny OpenAPI bug" width="800" height="287"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;IntelliJ indicating the method isn't used anywhere&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Obviously, the method &lt;em&gt;is&lt;/em&gt; used. But only in the Python template, where it is actually working as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class BaseApi(api_client.Api):
{{#if bodyParam}}
    {{#each getContentTypeToOperation}} // Our method here
    {{&amp;gt; endpoint_args_baseapi_wrapper contentType=@key this=this}}

    {{/each}}
    {{&amp;gt; endpoint_args_baseapi_wrapper contentType="null" this=this}}

{{else}}
    @typing.overload
    def _{{operationId}}_oapg(
    {{&amp;gt; endpoint_args isOverload=true skipDeserialization="False" contentType="null"}}
{{/if}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still, placing a breakpoint makes it clear enough that Jackson DOES enter the method when converting the object to JSON.&lt;/p&gt;

&lt;p&gt;I tried upgrading Jackson and other shenanigans, until &lt;a href="https://www.linkedin.com/in/alex-tarlovsky/" rel="noopener noreferrer"&gt;one of my colleagues&lt;/a&gt; made it all obvious : "Would your method start with &lt;code&gt;get&lt;/code&gt; or &lt;code&gt;set&lt;/code&gt; by any chance?".&lt;/p&gt;

&lt;p&gt;Finally, it all made sense : Jackson uses reflection to convert the object into JSON representation. Somehow, it takes the class and goes through all of the getters and setters iteratively. The method was assumed to be a getter, hence creating the crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the bug
&lt;/h2&gt;

&lt;p&gt;As usualy with software, once we know what the problem is it's actually quite easy to fix it. And even then, I went through 2 iterations.&lt;/p&gt;

&lt;p&gt;My first thought was to add the &lt;code&gt;@JsonIgnore&lt;/code&gt; annotation to the method, to tell Jackson to ignore it. It worked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
    @JsonIgnore // The magic line
    public Map&amp;lt;String, CodegenOperation&amp;gt; getContentTypeToOperation() {
        LinkedHashMap&amp;lt;String, CodegenOperation&amp;gt; contentTypeToOperation = new LinkedHashMap&amp;lt;&amp;gt;();
        if (bodyParam == null) {
            return null;
        }
        LinkedHashMap&amp;lt;String, CodegenMediaType&amp;gt; content = bodyParam.getContent();
        for (String contentType: content.keySet()) {
            contentTypeToOperation.put(contentType, this);
        }
        return contentTypeToOperation;
    }

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

&lt;/div&gt;



&lt;p&gt;I didn't quite like it though. It put some direct dependency to Jackson in a place there wasn't before, and there was no clear explanation either. The me from the future would have hated me for doing this.&lt;/p&gt;

&lt;p&gt;The second attempt was, I think, better and that's the one which got merged : Rename the method to remove the get 😅.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Map&amp;lt;String, CodegenOperation&amp;gt; contentTypeToOperation() {
        LinkedHashMap&amp;lt;String, CodegenOperation&amp;gt; contentTypeToOperation = new LinkedHashMap&amp;lt;&amp;gt;();
        if (bodyParam == null) {
            return null;
        }
        LinkedHashMap&amp;lt;String, CodegenMediaType&amp;gt; content = bodyParam.getContent();
        for (String contentType: content.keySet()) {
            contentTypeToOperation.put(contentType, this);
        }
        return contentTypeToOperation;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;_Method renamed! _&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/OpenAPITools/openapi-generator/pull/14331" rel="noopener noreferrer"&gt;William&lt;/a&gt; was nice enough &lt;a href="https://github.com/OpenAPITools/openapi-generator/pull/14331/files" rel="noopener noreferrer"&gt;to add a test to test for future regression&lt;/a&gt;. I wasn't sure how to do this (how to you test for "no crash of the JVM"). Well, you don't, because the crash isn't there. Here is his commit :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# test debugSupportingFiles
          ./bin/generate-samples.sh ./bin/configs/python.yaml -- --global-property debugSupportingFiles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A word of conclusion
&lt;/h2&gt;

&lt;p&gt;Most of what I've learnt about OpenAPI happened through this bug. The folks on Slack are super helpful and they've been gentle giving directions while expecting me to do the work.&lt;/p&gt;

&lt;p&gt;I write a lot of open code, but I don't work much in other people's libraries and that felt super nice. the bug&lt;a href="https://github.com/OpenAPITools/openapi-generator/issues/14161" rel="noopener noreferrer"&gt;should be closed&lt;/a&gt; in the coming weeks I expect.&lt;/p&gt;

&lt;p&gt;Thanks everyone, and happy new year already ! 🎉&lt;/p&gt;

&lt;p&gt;Don't hesitate to reach out if you have any questions! I'm mostly available on &lt;a href="https://mastodon.online/@jlengrand" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/julienlengrand/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; those days 😊, though you can still find me on &lt;a href="https://twitter.com/jlengrand" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;...&lt;/p&gt;

</description>
      <category>api</category>
      <category>openapi</category>
      <category>jackson</category>
      <category>java</category>
    </item>
    <item>
      <title>Getting started with OpenAPI Generators : tips and tricks</title>
      <dc:creator>Julien Lengrand-Lambert</dc:creator>
      <pubDate>Fri, 30 Dec 2022 23:57:08 +0000</pubDate>
      <link>https://dev.to/jlengrand/getting-started-with-openapi-generators-tips-and-tricks-4gdb</link>
      <guid>https://dev.to/jlengrand/getting-started-with-openapi-generators-tips-and-tricks-4gdb</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1623282033815-40b05d96c903%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwxMTc3M3wwfDF8c2VhcmNofDF8fGFwaXxlbnwwfHx8fDE2NzIxMzM4MzM%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1623282033815-40b05d96c903%3Fcrop%3Dentropy%26cs%3Dtinysrgb%26fit%3Dmax%26fm%3Djpg%26ixid%3DMnwxMTc3M3wwfDF8c2VhcmNofDF8fGFwaXxlbnwwfHx8fDE2NzIxMzM4MzM%26ixlib%3Drb-4.0.3%26q%3D80%26w%3D2000" alt="Getting started with OpenAPI Generators : tips and tricks" width="2000" height="1333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;TL;DR : In this article, I'm sharing some tips and tricks on how to get productive with creating OpenAPI generators&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lately, I've started dabbling more and more with &lt;a href="https://github.com/OAI/OpenAPI-Specification" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt;. The OpenAPI specification is a super useful way to describe the API that you're exposed to your users, both internally and externally.&lt;/p&gt;

&lt;p&gt;Some of you may also have heard about those under the name of "Swagger files". They're both kinda the same in the common speak, though OpenAPI is an open standard (lead with the OpenAPI Initiative) and Swagger is a set of tools built by a company (&lt;a href="https://smartbear.com/" rel="noopener noreferrer"&gt;SmartBear&lt;/a&gt;). Some of those tools are Open-Source, while others have a pro license. I won't go into the details of the specification here, there are plenty of great getting started resources, typically for different technologies. &lt;a href="https://www.baeldung.com/spring-rest-openapi-documentation" rel="noopener noreferrer"&gt;Here's one for Java and Spring&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generators&lt;/strong&gt; are the parts who take the OpenAPI model that's being created from a definition file, and generates server / client code and / or documentation. Or anything you want, really. There's &lt;a href="https://openapi-generator.tech/docs/generators" rel="noopener noreferrer"&gt;many generators&lt;/a&gt; available out of the box, so typically what you want is use or adapt one of them. It's not &lt;em&gt;always&lt;/em&gt; the case though. For example, there is currently no generator available for the &lt;a href="https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html" rel="noopener noreferrer"&gt;Jetbrains HTTP Client&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's where my story begins 😊, but that's for another day!&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the debugging flags
&lt;/h2&gt;

&lt;p&gt;Typically, if you're playing with a generator, you either want to generate a model file (your data structure), an operation file (your logic), or a supporting file (basically anything else, READMEs, docs, ...).&lt;/p&gt;

&lt;p&gt;At its core, the idea of OpenAPI is quite simple : It takes a specification file, transforms it into a set of objects in memory, and uses those objects to generates code / files using &lt;a href="http://mustache.github.io/" rel="noopener noreferrer"&gt;mustache&lt;/a&gt; template files. You can read more about it &lt;a href="https://openapi-generator.tech/docs/templating" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That's why it's crucial to have a good look into those objects, so that you can find what know where the data you need is located inside your mustache template. Here, debugging flags become vital. There's 3 of them, whether you want to see the data to generate models, operations or supporting files.&lt;/p&gt;

&lt;p&gt;You can use respectively &lt;code&gt;debugModels&lt;/code&gt;, &lt;code&gt;debugOpenAPI&lt;/code&gt; and /or &lt;code&gt;debugSupportingFiles&lt;/code&gt;. and you set them by running the desired  &lt;code&gt;generate&lt;/code&gt; command with the correct flag. For example&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ java -cp modules/openapi-generator-cli/target/openapi-generator-cli.jar org.openapitools.codegen.OpenAPIGenerator generate -g java -o out -i petstore.yaml --global-property debugModels=true

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

&lt;/div&gt;



&lt;p&gt;I won't print the entire output here because it's huge, but as part of the output it will basically spit a giant json representation of all the models available inside that specified yaml file. Here's a tiny part of the beginning :&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ {
  "importPath" : "org.openapitools.client.model.Category",
  "model" : {
    "anyOf" : [],
    "oneOf" : [],
    "allOf" : [],
    "name" : "Category",
    "classname" : "Category",
    "title" : "Pet category",
    "description" : "A category for a pet",
    "classVarName" : "category",
    "modelJson" : "{\n \"title\" : \"Pet category\",\n \"type\" : \"object\",\n \"properties\" : {\n \"id\" : {\n \"type\" : \"integer\",\n \"format\" : \"int64\"\n },\n \"name\" : {\n \"type\" : \"string\"\n }\n },\n \"description\" : \"A category for a pet\",\n \"xml\" : {\n \"name\" : \"Category\"\n }\n}",
    "dataType" : "Object",
    "xmlName" : "Category",
    "classFilename" : "Category",
    "unescapedDescription" : "A category for a pet",
    "isAlias" : false,
    "isString" : false,
    "isInteger" : false,
    "isLong" : false,
    "isNumber" : false,
    "isNumeric" : false,
    "isFloat" : false,
    "isDouble" : false,
    "isDate" : false,
    "isDateTime" : false,
    "isDecimal" : false,
    "isShort" : false,
    "isUnboundedInteger" : false,
    "isPrimitiveType" : false,
    "isBoolean" : false,
    "additionalPropertiesIsAnyType" : false,
    .........

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running and Debugging a generator
&lt;/h2&gt;

&lt;p&gt;Whether you want to play with an existing generator or &lt;a href="https://openapi-generator.tech/docs/new-generator/" rel="noopener noreferrer"&gt;create a new one&lt;/a&gt;, the existing documentation tends to offer you to compile and run the generators using the generic &lt;code&gt;./mvnw clean package&lt;/code&gt; followed by &lt;code&gt;./bin/generate-samples.sh bin/configs/spring-boot.yaml&lt;/code&gt; (replace with the file you want to use) command. The first one will compile the source, while the second one will use the created &lt;code&gt;openapi-generator-cli.jar&lt;/code&gt; and actually run the code.&lt;/p&gt;

&lt;p&gt;For example, the config file &lt;code&gt;spring-boot.yaml&lt;/code&gt; :&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generatorName: spring
outputDir: samples/server/petstore/springboot
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/JavaSpring
additionalProperties:
  artifactId: springboot

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

&lt;/div&gt;



&lt;p&gt;will run the command&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$./modules/openapi-generator-cli/target/openapi-generator-cli.jar org.openapitools.codegen.OpenAPIGenerator generate \
-g spring \
-i modules/openapi-generator/src/test/resources/2_0/petstore.yaml \
-o samples/server/petstore/springboot \
-t modules/openapi-generator/src/main/resources/JavaSpring \
--additional-properties=artifactId=springboot

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

&lt;/div&gt;



&lt;p&gt;Useful, but quite cumbersome. On top of this, you typically want to be able to run / debug code as you go directly in your IDE.&lt;/p&gt;

&lt;p&gt;Using IntelliJ, you can do it by creating a run configuration as such :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctgl3olwx001x5m3bkph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctgl3olwx001x5m3bkph.png" alt="Getting started with OpenAPI Generators : tips and tricks" width="800" height="483"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A run configuration in IntelliJ used to run a specific generator&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Set the &lt;code&gt;OpenAPIGenerator&lt;/code&gt; class to run from the &lt;code&gt;openapi-generator-cli&lt;/code&gt; jar file. Pick your generator name, input yaml file and output folder (the same as in the config file described above and set the working directory to be the root of the &lt;code&gt;openapi-generator&lt;/code&gt; git project. You're done!&lt;/p&gt;

&lt;p&gt;Now you can go in the generator's &lt;code&gt;CodeGen&lt;/code&gt; file, (for example &lt;code&gt;JMeterClientCodegen&lt;/code&gt; if your generator name is &lt;code&gt;jmeter&lt;/code&gt; ) and set breakpoints where you want 😊.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting breakpoints in the right locations
&lt;/h2&gt;

&lt;p&gt;As described above already OpenAPI generators take a specification file, transform it into a set of objects in memory, and use those objects to generates code / files using &lt;a href="http://mustache.github.io/" rel="noopener noreferrer"&gt;mustache&lt;/a&gt; template files.&lt;/p&gt;

&lt;p&gt;Typically, generators will take those existing objects and add or modify some of their content to fit the output to be generated. Most generators will extend from &lt;code&gt;DefaultCodeGen&lt;/code&gt;. That's where you will find the most useful locations to set breakpoints to see what to change and where.&lt;/p&gt;

&lt;p&gt;The most interesting part of this class is located in the &lt;code&gt;generate&lt;/code&gt; method. You will also be able to find the lines that are being used to print the debugging flags by searching for the &lt;code&gt;Json.prettyPrint&lt;/code&gt; calls. Here's an example for the models (as of now, &lt;a href="https://github.com/OpenAPITools/openapi-generator/blob/7c587ce061f1b2ef6bcd3fc406224452f79099a9/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java#L572" rel="noopener noreferrer"&gt;line 566 of&lt;/a&gt; &lt;a href="https://github.com/OpenAPITools/openapi-generator/blob/7c587ce061f1b2ef6bcd3fc406224452f79099a9/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java#L572" rel="noopener noreferrer"&gt;&lt;code&gt;DefaultCodeGen&lt;/code&gt;&lt;/a&gt; :&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (GlobalSettings.getProperty("debugModels") != null) {
    LOGGER.info("############ Model info ############");
    Json.prettyPrint(allModels);
}

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

&lt;/div&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fwans8pbc19yrp196kk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fwans8pbc19yrp196kk.png" alt="Getting started with OpenAPI Generators : tips and tricks" width="800" height="469"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A screenshot of a debug of the model, showing the content of the "allModels" variable&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Once you're in there, you can dive into the data and find out what you want to do with it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Extending the default generator
&lt;/h2&gt;

&lt;p&gt;(Thanks &lt;a href="https://twitter.com/beppecatanese" rel="noopener noreferrer"&gt;Beppe&lt;/a&gt; for the tip!)&lt;/p&gt;

&lt;p&gt;Most generators typically add extra bits of necessary information inside their objects, for example inside the bundle for supporting files : &lt;code&gt;bundle.put("distinctPathParameters", distinctPathParameters);&lt;/code&gt; . The trick is to find &lt;em&gt;where&lt;/em&gt; to do this.&lt;/p&gt;

&lt;p&gt;Luckily for us, the smart developers of OpenAPI have created locations to do just that! You'll want to search for the methods called &lt;code&gt;postProcess*&lt;/code&gt; in &lt;code&gt;DefaultCodeGen&lt;/code&gt;, those are placeholders that are set at the end of the various &lt;code&gt;generate&lt;/code&gt;methods that you can override at your convenience in your custom generator.&lt;/p&gt;

&lt;p&gt;Here is what a completely useless generator could do :&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Override
public Map&amp;lt;String, Object&amp;gt; postProcessSupportingFileData(Map&amp;lt;String, Object&amp;gt; bundle) {
    bundle.put("bloggingAt1AM", "isFun");

    return bundle;
}

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

&lt;/div&gt;




&lt;p&gt;Which you could then use inside a &lt;code&gt;README.mustache&lt;/code&gt; template, for example :&lt;/p&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Important announcement

{{bloggingAt1AM}} // Will print "isFun" once ran

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating custom lambdas
&lt;/h2&gt;

&lt;p&gt;One of the super powers of mustache is its lambdas, which you can declare in the templates. There is a &lt;a href="https://mustache.github.io/mustache.5.html" rel="noopener noreferrer"&gt;list available&lt;/a&gt; on the mustache website, but the OpenAPI templates define a few more. You can search the code for any class implementing the &lt;code&gt;Mustache.Lambda&lt;/code&gt; interface 😊.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fff96h1mqs7t7e7i81vqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fff96h1mqs7t7e7i81vqx.png" alt="Getting started with OpenAPI Generators : tips and tricks" width="644" height="802"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A list of many custom lambdas in the OpenAPI source code&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can easily create new lambdas as well by implementing that interface yourself.&lt;/p&gt;

&lt;p&gt;Here is a concrete example : Typically, parameters are surrounded with braces when using a generator. For example, for a GET request, with a &lt;code&gt;petId&lt;/code&gt; parameter it will come out like this : &lt;code&gt;DELETE http://petstore.swagger.io/v2/pet/{petId}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That being said, The &lt;a href="https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html" rel="noopener noreferrer"&gt;Jetbrains HTTP Client&lt;/a&gt; defines parameter with double braces (!). I need this to have a valid call : &lt;code&gt;DELETE http://petstore.swagger.io/v2/pet/{{petId}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of having to play around a lot with the templating, I decided to create a custom lambda for this in my generator. This is how it looks like :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class JetbrainsHttpClientClientCodegen extends DefaultCodegen implements CodegenConfig {

    @Override
    protected ImmutableMap.Builder&amp;lt;String, Mustache.Lambda&amp;gt; addMustacheLambdas() {
       return super.addMustacheLambdas()
                .put("doubleMustache", new DoubleMustacheLambda());
    }

    public static class DoubleMustacheLambda implements Mustache.Lambda {
        @Override
        public void execute(Template.Fragment fragment, Writer writer) throws IOException {
            String text = fragment.execute();
            writer.write(text
                    .replaceAll("\\{", "{{")
                    .replaceAll("}", "}}")
            );
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things happen here :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I create a &lt;code&gt;DoubleMustacheLambda&lt;/code&gt; class that implements &lt;code&gt;Mustache.Lambda&lt;/code&gt; that  itself implements the &lt;code&gt;execute&lt;/code&gt; method. The execute method does nothing else than rewriting some of the generated text.&lt;/li&gt;
&lt;li&gt;I override the &lt;code&gt;addMustacheLambdas&lt;/code&gt; method, and use it (just like for my data in the previous tip) to insert my custom lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that is done, I can use it inside my &lt;code&gt;api.mustache&lt;/code&gt; template! For example :  &lt;code&gt;{{#lambda.doubleMustache}}{{path}}{{/lambda.doubleMustache}}&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A word of conclusion
&lt;/h2&gt;

&lt;p&gt;I hope those tips will help you hit the ground running with OpenAPI generators. Most of the tips directly come from the open &lt;a href="https://join.slack.com/t/openapi-generator/shared_invite/zt-12jxxd7p2-XUeQM~4pzsU9x~eGLQqX2g" rel="noopener noreferrer"&gt;Slack channel&lt;/a&gt;, the folks there are super useful. The &lt;a href="https://openapi-generator.tech/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; is nice, but I honestly found it quite sparse, and once you dive in, a lot of the advice comes down to "have a look at the other generators for inspiration". I find it logical, because in 99% of the cases, the generator that you need already exists! If it doesn't though, you're in for some fun.&lt;/p&gt;

&lt;p&gt;Hopefully I'll save you a couple hours this way!&lt;/p&gt;

&lt;p&gt;Don't hesitate to reach out if you have any questions! I'm mostly available on &lt;a href="https://mastodon.online/@jlengrand" rel="noopener noreferrer"&gt;Mastodon&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/julienlengrand/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt; those days 😊, though you can still find me on &lt;a href="https://twitter.com/jlengrand" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;..&lt;/p&gt;

</description>
      <category>java</category>
      <category>openapi</category>
      <category>api</category>
      <category>development</category>
    </item>
  </channel>
</rss>
