<?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: martin-thomas-tm</title>
    <description>The latest articles on DEV Community by martin-thomas-tm (@martinthomastm).</description>
    <link>https://dev.to/martinthomastm</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F182486%2F40a77eb6-f8f5-4cf9-a8fe-f3c3358dc6b8.jpeg</url>
      <title>DEV Community: martin-thomas-tm</title>
      <link>https://dev.to/martinthomastm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martinthomastm"/>
    <language>en</language>
    <item>
      <title>Prochainement au BreizhCamp: Cartographions l’univers de Star Wars avec Neo4J</title>
      <dc:creator>martin-thomas-tm</dc:creator>
      <pubDate>Tue, 18 Jun 2024 10:26:27 +0000</pubDate>
      <link>https://dev.to/martinthomastm/prochainement-au-breizhcamp-cartographions-lunivers-de-star-wars-avec-neo4j-5ddn</link>
      <guid>https://dev.to/martinthomastm/prochainement-au-breizhcamp-cartographions-lunivers-de-star-wars-avec-neo4j-5ddn</guid>
      <description>&lt;p&gt;En Juin dernier, j’ai présenté un talk sur l’API Versioning. Vous pouvez retrouver ce talk en vidéo ici.&lt;/p&gt;

&lt;p&gt;J’aurais le plaisir cette année d’être accompagné au BreaizhCamp par Mickaël Barroux pour un talk sur Neo4J.&lt;/p&gt;

&lt;p&gt;Rejoignez-nous dans une galaxie pas si lointaine au cœur de Neo4j, la base de données graphe qui améliore notre compréhension des constellations de données. Lors de cette conférence, nous tracerons notre route à travers les étoiles en suivant ce plan de vol : Comprendre les concepts fondamentaux qui sous-tendent les bases de données graphes. Apprendre à représenter l’univers Star Wars en termes de systèmes et planètes au sein de Neo4j. Maîtriser le langage de requête Cypher pour interroger et manipuler notre galaxie de données. Découvrir les outils qui facilitent le travail avec Neo4j, permettant de visualiser et d’interagir avec nos données spatiales. Identifier ce qui distingue Neo4j des bases de données traditionnelles et pourquoi elle est idéale pour cartographier des relations complexes. Que la Force des graphes soit avec vous pour explorer les liens cachés entre les systèmes et planètes de notre propre galaxie de données !&lt;/p&gt;

&lt;p&gt;Le BreizhCamp aura lieu du 26 au 28 Juin à Rennes, notre talk sera le vendredi 28 Juin à 11h30.&lt;/p&gt;

&lt;p&gt;Au plaisir de se rencontrer !&lt;/p&gt;

&lt;p&gt;Note: this article is exceptionally written in French because the talks are in French, the next articles will be in English as usual.&lt;/p&gt;

</description>
      <category>talks</category>
      <category>neo4j</category>
      <category>breizhcamp</category>
    </item>
    <item>
      <title>How we handle breaking changes in Whoz API thanks to Spring Cloud Gateway</title>
      <dc:creator>martin-thomas-tm</dc:creator>
      <pubDate>Thu, 11 Jan 2024 13:18:06 +0000</pubDate>
      <link>https://dev.to/martinthomastm/how-we-handle-breaking-changes-in-whoz-api-thanks-to-spring-cloud-gateway-3f37</link>
      <guid>https://dev.to/martinthomastm/how-we-handle-breaking-changes-in-whoz-api-thanks-to-spring-cloud-gateway-3f37</guid>
      <description>&lt;p&gt;At &lt;a href="https://www.whoz.com/"&gt;Whoz&lt;/a&gt;, like most SaaS services, we expose an API so our clients can import or export their data and interconnect all their software. And at Whoz, like most API providers, we had to face the difficulty of API versioning and managing breaking changes.&lt;/p&gt;

&lt;p&gt;In this article, I’ll present the solution we came up with, based on Spring Cloud Gateway and a bit of our self-cooked sauce. Of course, it has its limitations and has been subject to a lot of internal debates, but this is the best way we found, knowing that at the end of the day &lt;a href="https://apisyouwonthate.com/blog/api-versioning-has-no-right-way/"&gt;API versioning has no “right” way&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  API versioning really?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--saf01VKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6uqzmuvzs2a2we3a7wx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--saf01VKm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6uqzmuvzs2a2we3a7wx8.png" alt="“Why ?” is always a good question" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before getting into the details, you could legitimately wonder why API versions are needed? Until a few months ago, we were dealing with breaking changes without any versioning. Breaking changes were avoided unless absolutely needed, and when they were, the deprecated properties were kept with some in-app code to maintain them by translating the new properties into the old ones. Because our API is consumed by customer clients but also and mainly our front-end app, things were getting tricky.&lt;/p&gt;

&lt;p&gt;Developers were not happy with all this backward compatibility code mixed with the new shiny code of their latest feature. Product owners were not happy with developers telling them that their awesome feature is “impossible” or costs three times the usual cost. Customers would not have been happy if the app was getting slower because payload sizes were always increasing because of deprecated fields. Basically, technical debt was increasing without much we could do about it without versioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design goals
&lt;/h2&gt;

&lt;p&gt;Once we decided to implement an API versioning system, we settled on several goals to reach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep It Simple&lt;/li&gt;
&lt;li&gt;Keep the business code free from backward compatibility considerations&lt;/li&gt;
&lt;li&gt;Handle all the back-end microservices&lt;/li&gt;
&lt;li&gt;Be seamless for current API customers&lt;/li&gt;
&lt;li&gt;Have a “private” API that is subject to breaking changes every other week (we work in two-weeks-long sprints)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also had issues to tackle about monitoring deprecated versions usage, documentation, release cycle, or communication but that’s for another story.&lt;/p&gt;




&lt;h2&gt;
  
  
  The “Accept-Version” header
&lt;/h2&gt;

&lt;p&gt;Clients choose the API version thanks to the &lt;code&gt;Accept-Version&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;It is assumed to mean “V1” version when no header is passed. That way, clients implemented prior to versioning still work.&lt;/p&gt;

&lt;p&gt;To avoid splitting our API into a private and a public one, increasing the maintaining cost, we use a special API version that is unstable and is called &lt;code&gt;latest&lt;/code&gt;. &lt;code&gt;latest&lt;/code&gt; is translated by the versioning system into the next version.&lt;/p&gt;

&lt;p&gt;For instance, if the current stable version is “V3”, we have the following API versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;V1&lt;/strong&gt; (default if no &lt;code&gt;Accept-Version&lt;/code&gt; header)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V2&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V3&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V4&lt;/strong&gt; (unstable, also targetted if &lt;code&gt;Accept-Version="latest"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Note that “unstable” here does not mean beta, it is a fully functioning version used by the front-end. However, it may introduce breaking changes without notice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the release date of &lt;strong&gt;V4&lt;/strong&gt; is reached, we simply need to update the documentation and introduce a &lt;strong&gt;V5&lt;/strong&gt; API version into the system, becoming de facto the new unstable version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Successive transformations
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JeaYnyIo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o703efiiym6pl14tgq8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JeaYnyIo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o703efiiym6pl14tgq8z.png" alt="Step by step" width="800" height="1002"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The general idea of the versioning system is to transform requests and responses from one version to the next one, reaching step by step the latest API version which is the current state of the back-end services.&lt;/p&gt;

&lt;p&gt;That means that we only have to transform payloads from the last stable version to the unstable one when introducing breaking changes.&lt;/p&gt;

&lt;p&gt;Transformations are handled by Spring beans implementing the &lt;code&gt;VersionTransformer&lt;/code&gt; interface, which the Kotlin code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;VersionTransformer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;transformResponseObjectNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;transformRequestObjectNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sourceVersionNumber&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;ApiVersion&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ServerWebExchange&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first two methods are the obvious transformation methods for requests and responses.&lt;/p&gt;

&lt;p&gt;The last two methods will be called by the &lt;code&gt;ApiVersioningManager&lt;/code&gt; to determine if the transformer must be applied or not. The &lt;code&gt;accept()&lt;/code&gt; method is a condition to apply the transformer, typically on the beginning of the path which indicates the business object managed by the endpoint. The &lt;code&gt;sourceVersionNumber()&lt;/code&gt; method defines which API version the input of the transformer uses. Since transformers handle only the transformations to the next version, it is unnecessary to specify the target version. The same is true for the name of the transformer classes. For instance, the &lt;code&gt;TaskV2Transformer&lt;/code&gt; handles call to all endpoints under the path &lt;code&gt;/api/task&lt;/code&gt; with a &lt;code&gt;V2&lt;/code&gt; API version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;V2TaskTransformer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VersionTransformer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;sourceVersionNumber&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;ApiVersion&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApiVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;V2&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ServerWebExchange&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/task"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;TaskV2Transformer&lt;/code&gt; would also be called in the case of a request with &lt;code&gt;Accept-Version="V1"&lt;/code&gt; because the transformers are called successively. Let’s pretend that there is a breaking change on task endpoints between V2 and V3, and another one between V3 and V4, the transformers would be applied in that order by the &lt;code&gt;ApiVersionManager&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;TaskV2Transformer.transformRequestObjectNode()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskV3Transformer.transformRequestObjectNode()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The back-end service&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskV3Transformer.transformResponseObjectNode()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TaskV2Transformer.transformResponseObjectNode()&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aQwcypj9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qold9txbtooc55kvk9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aQwcypj9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7qold9txbtooc55kvk9p.png" alt="TaskV1Transformer does not exist because there is no breaking change between V1 and V2&lt;br&gt;
" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now, this covers all our use cases.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;accept()&lt;/code&gt; method is quite generic and future-proof, allowing other eligibility criteria, such as payload patterns or precise endpoint names. Breaking changes are thankfully sparse, so one transformer for each version and business object is manageable, but the interface is generic enough to have one transformer per modified field.&lt;/p&gt;

&lt;p&gt;One more questionable design choice is the fact that transformers handle request and response transformation. What about read-only fields? Just do nothing in the request part? As long as we don’t encounter such issues, we’ll keep the interface as is for simplicity's sake, but I can sense we’ll have to change it sooner or later.&lt;/p&gt;
&lt;h2&gt;
  
  
  The gateway
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hrt_cUHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/592uohtc7bvgdikrgl7q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hrt_cUHf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/592uohtc7bvgdikrgl7q.jpg" alt="Gateway — source: [Laila Gebhard](https://unsplash.com/@lailagebhard?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText) on Unsplash" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We naturally have a gateway between clients and back-end services to intercept the requests and responses. Historically, that gateway was based on &lt;a href="https://github.com/Netflix/zuul"&gt;Zuul&lt;/a&gt;. Since then, Spring has released the great &lt;a href="https://spring.io/projects/spring-cloud-gateway/"&gt;Spring Cloud Gateway&lt;/a&gt; and has deprecated Zuul. Our stack is based on Spring Boot, so we decided to port the gateway to that new technology.&lt;/p&gt;

&lt;p&gt;Like Zuul, Spring Cloud Gateway allows creating filters to be applied pre and post-backend calls. There are two filters to handle the transformations : &lt;code&gt;ApiVersioningInput&lt;/code&gt; and &lt;code&gt;ApiVersioningOutput&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the following groovy code, you can see that we configure a &lt;code&gt;ModifyRequestBodyGatewayFilterFactoryto&lt;/code&gt; call our &lt;code&gt;ApiVersioningManager&lt;/code&gt; into a &lt;code&gt;Mono&lt;/code&gt;, because Spring Cloud Gateway is based on Spring Web Flux that is itself built upon the &lt;a href="https://projectreactor.io/"&gt;Reactor&lt;/a&gt; reactive library. This is right for our API, though I’m not sure a &lt;code&gt;Mono&lt;/code&gt; would work in the case of web sockets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIVersioningInputGatewayFilterFactory&lt;/span&gt; &lt;span class="n"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AbstractGatewayFilterFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="nc"&gt;ApiVersioningManager&lt;/span&gt; &lt;span class="n"&gt;apiVersioningManager&lt;/span&gt;
    &lt;span class="nc"&gt;ModifyRequestBodyGatewayFilterFactory&lt;/span&gt; &lt;span class="n"&gt;modifyRequestBodyGatewayFilterFactoryDelegate&lt;/span&gt;

    &lt;span class="nc"&gt;APIVersioningInputGatewayFilterFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;modifyRequestBodyGatewayFilterFactoryDelegate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModifyRequestBodyGatewayFilterFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="nc"&gt;GatewayFilter&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;GatewayFilter&lt;/span&gt; &lt;span class="n"&gt;modifyRequestBodyGatewayFilterDelegate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;modifyRequestBodyGatewayFilterFactoryDelegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ModifyRequestBodyGatewayFilterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;inClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;outClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;rewriteFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RewriteFunction&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;byte[]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;byte[]&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;@Override&lt;/span&gt;
                &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;byte[]&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerWebExchange&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;originalBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originalBody&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;just&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiVersioningManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;applyRequestTransformations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originalBody&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerWebExchange&lt;/span&gt; &lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GatewayFilterChain&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiVersioningManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkNeedForRequestTransformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;modifyRequestBodyGatewayFilterDelegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyNoChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;applyNoChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerWebExchange&lt;/span&gt; &lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GatewayFilterChain&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the configuration, the list of filters will have an automatically assigned &lt;code&gt;order&lt;/code&gt; (&lt;code&gt;1&lt;/code&gt; for the first one, &lt;code&gt;2&lt;/code&gt; for the second, etc.). The filters will be applied in ascending order in the request processing, and in descending order in the response processing. We have this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default-filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SomethingAuthRelated&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SomethingUnrelatedToVersioning&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;APIVersioningInput&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;APIVersioningOutput&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well for the request part, but not the response part. Indeed, the filter writing the response has the order, and we need to transform the payload before that. To do so, we set the order ourselves with an &lt;code&gt;OrderedGatewayFilter&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="nc"&gt;GatewayFilter&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// about the same code as the request part ...&lt;/span&gt;

        &lt;span class="c1"&gt;// filter must be executed after WRITE_RESPONSE_FILTER_ORDER so the response rewriting is done AFTER the backend call&lt;/span&gt;
        &lt;span class="c1"&gt;// this order is the one of the ModifyResponseBodyGatewayFilterFactory&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OrderedGatewayFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerWebExchange&lt;/span&gt; &lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;GatewayFilterChain&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiVersioningManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkNeedForTransformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;modifyResponseBodyFilterDelegate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyNoChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceExchange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceChain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nc"&gt;NettyWriteResponseFilter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WRITE_RESPONSE_FILTER_ORDER&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Transformers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nUnsuloN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8zvfsyuddc7nxmh1q41h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nUnsuloN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8zvfsyuddc7nxmh1q41h.jpg" alt="Sorry but I had to :) — source: Netflix" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interface of &lt;code&gt;VersionTransformer&lt;/code&gt; is fairly simple, so are their implementations. As I write those lines, all they do is manipulating JSON payloads thanks to &lt;a href="https://github.com/FasterXML/jackson"&gt;Jackson&lt;/a&gt;'s &lt;code&gt;ObjectNode&lt;/code&gt; and its subclasses.&lt;/p&gt;

&lt;p&gt;We don’t do serialization to POJOs because:&lt;/p&gt;

&lt;p&gt;that would become painful to maintain to have a different POJO for each version of a business object&lt;br&gt;
the less overhead the better. Keep in mind that the transformers are part of a gateway that is passed through at least twice for each API call, so we want them to be as fast as possible.&lt;br&gt;
For example, look at the code to rename the &lt;code&gt;aphotecary&lt;/code&gt; property to &lt;code&gt;drugstore&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;   &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;transformRequestObjectNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;apothecary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"apothecary"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JsonNode&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"drugstore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;apothecary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"apothecary"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requestBody&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;transformResponseObjectNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ObjectNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;drugstore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"drugstore"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"drugstore"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;responseBody&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JsonNode&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"apothecary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drugstore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;responseBody&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have not needed anything more than JSON transformations for the first few months. We’ll probably have to do trickier things in the future like endpoint splitting, but for as long as this simple design will work, we’ll &lt;a href="https://en.wikipedia.org/wiki/KISS_principle"&gt;keep it simple&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;API versioning can be headache-inducing. I hope you’ve enjoyed reading our adventure with tackling it as much as I enjoyed writing it and that it will be helpful for your own journey. And if you already have gotten through this, feel free to share your experience too!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pictures credits: &lt;a href="https://unsplash.com/@chrislawton?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Chris Lawton&lt;/a&gt; on Unsplash,Harold &amp;amp; Kumar Go to White Castle, my Instagram, &lt;a href="https://unsplash.com/@lailagebhard?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Laila Gebhard&lt;/a&gt; on Unsplash, Netflix&lt;/em&gt;&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>api</category>
      <category>apigateway</category>
      <category>apiversioning</category>
    </item>
  </channel>
</rss>
