{"data":{"article":{"id":"99158851-0237-5904-98ca-02b5e2f45684","frontmatter":{"category":"Coding","title":"Spring 5 candidate component index case study","date":"2019-02-05","summary":"Analysis of Spring 5.X candidate component index applicability to boost our application startup time","thumbnail":{"relativePath":"pages/spring-5-indexer/thumbnailImage.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAZABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAeBWySoZgAf/xAAaEAACAgMAAAAAAAAAAAAAAAAAARASICEx/9oACAEBAAEFAisLg95//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFBABAAAAAAAAAAAAAAAAAAAAMP/aAAgBAQAGPwJP/8QAGhAAAwADAQAAAAAAAAAAAAAAAAERECAxIf/aAAgBAQABPyFK8Kx5obfaNVLt/9oADAMBAAIAAwAAABBTBzz/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAcEAEAAgMBAQEAAAAAAAAAAAABABEhMUFRYSD/2gAIAQEAAT8QR0FfCY14dprVxKUeSiVurchzX3sU1d7c6ZqWULr2CBDvz9f/2Q==","width":265,"height":325,"src":"/static/85788e7deb4eaccc84801ebd1f7b77a8/a2998/thumbnailImage.jpg","srcSet":"/static/85788e7deb4eaccc84801ebd1f7b77a8/a2998/thumbnailImage.jpg 1x,\n/static/85788e7deb4eaccc84801ebd1f7b77a8/99cce/thumbnailImage.jpg 1.5x"}}},"authorName":"Mariusz Sondecki","authorDescription":"Mariusz is Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/spring-5-indexer/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACyElEQVQ4y4VUO2gUURR9mZmN8YcItoKVjeXuzsxu9jOzm92NkBBTqDEmjZAEgtqIpSgI1gp+uhS6kEI7wc7GQsFCLERiISGCaGHhF02yu+M5s/cujyC4cJY379573r3n3XdNvV51xsYiJ4rKbqlUyBj8wjAYyWazp4BVYB3YAraBDeAhMBcE/l76lkrFTLVachuNyCGXEbJMrVbx6ADnaWAtn88nvu8nWBMdQcJ9Auv3wCxjWq2aC9IMSF3DzCyyG1YAs9pSYkL2melmLpfT71tKGkUlz2iZJAuCgA5dEjGAwPoV0AbuAy+tfR7YkZjb5CiXR1OutEw5jWQdCfgCnEiSxNg/7LWAj+KTSiHSzBvrAtascrriPCmnDkP4AYQ0Em3TGPHf8P38ARpP/4PsHQ7aU69Hqdi4OJeI47JXLg9IX1tVbUuWCzSsygcJO+L0goGNRpySoB1colarerhEvcCn4tuxCB/TsC5ZdS3Ct2Ho75uZmRoigWbIbgCxt7Q0O8TLsgg17o2R2+JHzzJcV/1IoBlybel42SLsCcc3I6UqoaZ+lUHFYjjCzJSQa+4J4UVLKiX8ScOGVXJXDN+xd7SfZXFYS65U+n2Wz+cOw+ezHadSkfCRdVJiZdlmMG9ZtHMpgWR3Z0eMSvWExjmrbXoCbZ951dIim7Yk6u1IYtlwavChW51Pp01tXOwf01eC9RHs/Rbbnx2Hf4IUh/Q5zdol8H3KG22Pj9edOK54LF1876pd37TELhqdZ+J4MwxDHUvXgDOFQrB7YqLpjI4WdhG8GLYNbCeBK/1+TWNWRG+Pfy5Hj5Dek3IXzX9+8DkL/AIeLC/PDU1NHXdSQk5altNsDkjPAT+ArzKyFnK5bB2IxbYik4hanmfM5GTL6Q/Y2DUc25y0HLToubR8TI39cL4APJOxr/35AXgOXEJnHKQvepP6eiQj119kDrZjqLo8HAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png","srcSet":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png 1x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/eb85b/avatar.png 1.5x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/4f71c/avatar.png 2x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/9ec3e/avatar.png 3x"}}},"headerImage":{"relativePath":"pages/spring-5-indexer/headerImage.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBv/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAc/UgxB//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQABBQJf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAGBAAAwEBAAAAAAAAAAAAAAAAAAERIEH/2gAIAQEAAT8hElKxzmP/2gAMAwEAAgADAAAAEPPP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxAAAgIDAQAAAAAAAAAAAAAAAREAECExQVH/2gAIAQEAAT8Qm5fABBZ6Hh5TKTxX/9k=","width":1280,"height":853,"src":"/static/430c76d0d1e83aad3065e86e11366218/966a5/headerImage.jpg","srcSet":"/static/430c76d0d1e83aad3065e86e11366218/966a5/headerImage.jpg 1x,\n/static/430c76d0d1e83aad3065e86e11366218/aa36d/headerImage.jpg 1.5x"}}}},"html":"<h2>The situation</h2>\n<p>Since day one at AUTO1, we strongly believe that the microservice architecture helps us run business at scale not only due to the technical benefits it brings but also, if not foremost by allowing many teams to work independently from each other. Today, we’re running more than 250 microservices in production and on average add one more to our platform every week. All of them are powered by the Spring Framework technology stack, which we value very much for its productivity, but at the same time consider it to be a bit heavy in terms of bootstrapping time. The typical startup time of our Spring applications varies between 40 and 80 seconds. Since we do around 30 deployments per day, our engineering teams spend roughly 30 minutes daily waiting for the deployments to be completed. As we value the time of our engineering teams, we wanted to find a way to decrease these numbers. </p>\n<h2>A new hope</h2>\n<p>While investigating the startup times of our applications, we have identified classpath component scanning as a potential place to look for improvements. Luckily for us, some of our tech savvy engineers suggested a not so prominent Spring 5.X feature to come at help here - the build time component candidate index.\nThe build time component index is an alternative for regular component scanning, which skips the classpath search operation in favor of using a compile time pre-generated index of component candidates during the spring application context building. However, it comes with a small print from the Spring Framework experts saying that this functionality should have mostly a visible impact on startup times for applications with large amount of beans and that are operating in environments where IO operations are expensive (e.g. remote file systems) or where JVM security managers are in use. Although, neither of this is our use case, we have decided to proceed with the feature evaluation and measurements, as here at AUTO1, we like our decisions to be data driven.</p>\n<h2>Demystifying the indexer</h2>\n<p>The key part of the indexer feature is a pre-generated index file located in <em><span style=\"font-family:Courier New;\">META-INF/spring.components</span></em> one per JAR. Every index entry in this file is a fully qualified name of a candidate component as a key and comma separated stereotypes as value. So for example “X=Y, Z” can be read simply as register a candidate component X with following stereotypes Y, Z. Below is an example of a <em><span style=\"font-family:Courier New;\">spring.components</span></em> file:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">com.auto1.playground.service.SpringDummyService=org.springframework.stereotype.Component\ncom.auto1.playground.domain.SpringDummy=javax.persistence.Entity,javax.persistence.Table,javax.persistence.EntityListeners\ncom.auto1.playground.repository.SpringDummyRepository=org.springframework.stereotype.Component,org.springframework.data.repository.Repository\ncom.auto1.playground.configuration.LocalConfiguration=org.springframework.stereotype.Component\ncom.auto1.playground.service.IndexedCustomService=com.auto1.playground.service.IndexedCustomService\ncom.auto1.playground.Application=org.springframework.stereotype.Component\ncom.auto1.playground.SpringDummyController=org.springframework.stereotype.Component\ncom.auto1.playground.service.IndexedCustomExample=com.auto1.playground.annotation.IndexedCustom</code></pre></div>\n<p>The first question that arises here is how to create such a file and keep it up-to-date. The Spring engineers thought about this problem as well and came up with an annotation processor tool called <a href=\"https://github.com/spring-projects/spring-framework/tree/master/spring-context-indexer\">spring indexer</a> that hooks in during the project build phase and generates the file. Making use of it is fairly simple, assuming you are using your favorite build automation tool, in our case maven and want our IDE to keep this file up to date, simply add the spring indexer annotation processor to your pom file:</p>\n<div class=\"gatsby-highlight\" data-language=\"xml\"><pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>build</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>plugins</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>plugin</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>groupId</span><span class=\"token punctuation\">></span></span>org.apache.maven.plugins<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>groupId</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>artifactId</span><span class=\"token punctuation\">></span></span>maven-compiler-plugin<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>artifactId</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>version</span><span class=\"token punctuation\">></span></span>3.5<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>version</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>configuration</span><span class=\"token punctuation\">></span></span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>annotationProcessorPaths</span><span class=\"token punctuation\">></span></span>\n                    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>path</span><span class=\"token punctuation\">></span></span>\n                        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>groupId</span><span class=\"token punctuation\">></span></span>org.springframework<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>groupId</span><span class=\"token punctuation\">></span></span>\n                        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>artifactId</span><span class=\"token punctuation\">></span></span>spring-context-indexer<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>artifactId</span><span class=\"token punctuation\">></span></span>\n                    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>path</span><span class=\"token punctuation\">></span></span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>annotationProcessorPaths</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>configuration</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>plugin</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>plugins</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>build</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>Once this is done, what happens during project build time is the spring context indexer (precisely speaking the <a href=\"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/index/CandidateComponentsIndex.html\">CandidateComponentsIndexer</a> class) while going through the project identifies annotated components and outputs them to <em><span style=\"font-family:Courier New;\">META-INF/spring.components</span></em>. The type of components it includes in the candidate list are:</p>\n<ol>\n<li>\n<p>Classes annotated directly or indirectly with spring <em><span style=\"font-family:Courier New;\">@Component</span></em> annotation (which by itself is meta annotated with <em><span style=\"font-family:Courier New;\">@Indexed</span></em>) and other stereotypes for which <em><span style=\"font-family:Courier New;\">@Component</span></em> is a meta annotation such as <em><span style=\"font-family:Courier New;\">@Repository</span></em>, <em><span style=\"font-family:Courier New;\">@Controller</span></em>, <em><span style=\"font-family:Courier New;\">@Service</span></em>, <em><span style=\"font-family:Courier New;\">@Configuration</span></em>.</p>\n</li>\n<li>\n<p>Classes and interfaces annotated with annotations from <em><span style=\"font-family:Courier New;\">javax</span></em> package, including <em><span style=\"font-family:Courier New;\">CDI</span></em> annotations (<em><span style=\"font-family:Courier New;\">@Named<span></em>, <em><span style=\"font-family:Courier New;\">@ManagedBean</span></em>), <em><span style=\"font-family:Courier New;\">JPA</span></em> annotations (<em><span style=\"font-family:Courier New;\">@Entity</span></em>, <em><span style=\"font-family:Courier New;\">@EntityListeners</span></em>) or even <em><span style=\"font-family:Courier New;\">Servlet</span></em> annotations (<em><span style=\"font-family:Courier New;\">@WebListener</span></em>)</p>\n</li>\n<li>\n<p>Any custom classes and interfaces annotated with <em><span style=\"font-family:Courier New;\">@Indexed</span></em> annotation. </p>\n</li>\n</ol>\n<p>It should be noted that applying <em><span style=\"font-family:Courier New;\">@Indexed</span></em> on a class/interface where no other standard stereotype (as from points 1 and 2) is present will not make by itself the bean managed, thus not making them eligible for being autowired, but merely provide a hint that it represents a stereotype to be considered in the index. </p>\n<p>Example of the <em><span style=\"font-family:Courier New;\">@Indexed</span></em> annotation usage:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token annotation punctuation\">@Indexed</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">IndexedCustomService</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span></code></pre></div>\n<p>Or as meta annotated as below: </p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token annotation punctuation\">@Target</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ElementType</span><span class=\"token punctuation\">.</span>TYPE<span class=\"token punctuation\">)</span>\n<span class=\"token annotation punctuation\">@Retention</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">RetentionPolicy</span><span class=\"token punctuation\">.</span>RUNTIME<span class=\"token punctuation\">)</span>\n<span class=\"token annotation punctuation\">@Documented</span>\n<span class=\"token annotation punctuation\">@Indexed</span>\n<span class=\"token keyword\">public</span> <span class=\"token annotation punctuation\">@interface</span> <span class=\"token class-name\">IndexedCustom</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span>\n\n<span class=\"token annotation punctuation\">@IndexedCustom</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">IndexedCustomExample</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span></code></pre></div>\n<p>In order to make them available in the application context, thus autowirable, we could for example include them in the component scan filters as shown below: </p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token annotation punctuation\">@SpringBootApplication</span>\n<span class=\"token annotation punctuation\">@ComponentScan</span><span class=\"token punctuation\">(</span>basePackages<span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"com.auto1\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> includeFilters <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation punctuation\">@ComponentScan.Filter</span><span class=\"token punctuation\">(</span>type<span class=\"token operator\">=</span> ANNOTATION<span class=\"token punctuation\">,</span> value <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token class-name\">IndexedCustomExample</span><span class=\"token punctuation\">.</span><span class=\"token keyword\">class</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token annotation punctuation\">@ComponentScan.Filter</span><span class=\"token punctuation\">(</span>type<span class=\"token operator\">=</span> ASSIGNABLE_TYPE<span class=\"token punctuation\">,</span> value <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token class-name\">IndexedCustomService</span><span class=\"token punctuation\">.</span><span class=\"token keyword\">class</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Application</span> <span class=\"token keyword\">implements</span> <span class=\"token class-name\">WebMvcConfigurer</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// ...</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>There is one big pitfall that comes with the feature though, that is by default the index is automatically enabled when a <em><span style=\"font-family:Courier New;\">META-INF/spring.components</span></em> file is found on the classpath during application start-up and this in turn, disables completely the regular classpath component scanning. In other words, both the index and classpath scanning cannot be used at the same time. The consequence of this is that if an index is available for some JARs, but is incomplete due to including other JARs that come with potential bean candidates but don’t have the index file generated, those beans will not be discovered, thus application startup might spectacularly fail with a well known error: <em><span style=\"font-family:Courier New;\">“X required a bean of type 'Y’ that could not be found”</span></em>.\nFortunately, Spring engineers, as thoughtful as they are, provided a feature flag <em><span style=\"font-family:Courier New;\">spring.index.ignore</span></em> that can be passed either as a system property or set in the <em><span style=\"font-family:Courier New;\">spring.properties</span></em> file at the root of the classpath allowing us to disable the usage of the candidate component index altogether. </p>\n<h2>Test me one more time</h2>\n<p>Like mentioned before, the aim of our investigation was to understand if the Spring indexer feature is of any help to us in regards to boosting the application start-up times. In order to make the test fairly reliable and control as many confounding factors as possible, we have decided to run the test firstly, using a “dummy” Spring 5 application (in two flavors - with 120 and 5000 beans) using all the features a production microservice would use, on a bare metal machine i.e. a <span style=\"font-family:Courier New;\">MacBook Pro late 2017 i7</span>. Secondly, running a real life production service in a less controlled but close to production environment i.e. on a <span style=\"font-family:Courier New;\">r4.8xlarge</span> EC2 instance with other services running concurrently. The applications themselves were using <span style=\"font-family:Courier New;\">Spring 5.0.10.RELEASE</span> with <span style=\"font-family:Courier New;\">Spring Cloud Finchley.SR2</span> release train.\nAfter making a couple of hundred measurements of application bootstrapping times with candidate component index enabled / disabled and verifying in the logs that Spring was indeed using (or not) the component candidate list, we ended up with following averaged results:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/30322fc0fcb831c8c776c8ac7df43562/a0e60/measurements.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 70%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMCBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB3qb1i4kM/wD/xAAaEAACAwEBAAAAAAAAAAAAAAABAgAREhMD/9oACAEBAAEFArnUxDaP56PGKMr/AP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABgQAQEAAwAAAAAAAAAAAAAAAAABEDFB/9oACAEBAAY/AnEuNpH/xAAYEAEBAQEBAAAAAAAAAAAAAAABEQAhMf/aAAgBAQABPyFB6husmO16mK1mi2t3Gw3/2gAMAwEAAgADAAAAEJM//8QAGhEAAgIDAAAAAAAAAAAAAAAAABEBIYHh8P/aAAgBAwEBPxCF2dENWf/EABoRAAICAwAAAAAAAAAAAAAAAAARASGB4fD/2gAIAQIBAT8Ql9jZKdH/xAAdEAEBAAEEAwAAAAAAAAAAAAABEQAhMUFhccHR/9oACAEBAAE/EF55xmDyDZWb6p6yXcazAScAaHdwDquO37giISrzn//Z'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Measurements\"\n        title=\"\"\n        src=\"/static/30322fc0fcb831c8c776c8ac7df43562/f8fb9/measurements.jpg\"\n        srcset=\"/static/30322fc0fcb831c8c776c8ac7df43562/e8976/measurements.jpg 148w,\n/static/30322fc0fcb831c8c776c8ac7df43562/63df2/measurements.jpg 295w,\n/static/30322fc0fcb831c8c776c8ac7df43562/f8fb9/measurements.jpg 590w,\n/static/30322fc0fcb831c8c776c8ac7df43562/a0e60/measurements.jpg 800w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<h2>The expected disappointment</h2>\n<p>As the above results have consistently shown, there was no improvement or even insignificant worsening (which will be a subject of our analysis in a separate article) of the bootstrapping time of the Spring applications, regardless of the number of candidate beans present, when using the pre-generated candidate component index. This was expected, as explained in the opening section, the conditions for which this feature was designed (slow or expensive IO operations) are simply not met in our case. One more important factor to consider here, is that a great deal of the beans in our Spring applications, is created using bean definition factory methods, which are not subject to the component classpath scanning, thus are not impacted by this feature. </p>\n<p>All in all, we deemed this feature not to be useful for our purposes and moved on to the next item on our list.</p>","fields":{"slug":"/spring-5-indexer/","tags":["spring","spring-indexer","bootstrapping","performance"]}},"categoryArticles":{"edges":[{"node":{"id":"f3a0fb4b-eb2f-5624-a408-d63e3cee5184","frontmatter":{"category":"Coding","title":"Spring batch integration","date":"2020-02-07","summary":"Usage cases for spring-batch.","thumbnail":null,"authorName":"Artur Yolchyan","authorDescription":"Artur is a Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/spring-batch-integration/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAEfElEQVQ4yz2O2VMbBQDGlzJQSylXIEc3F0eODYGEJAQI5GBzbK49kt0cm703CSRQqfQCB9taap1qgY61VXqoPZxxbKe2dWo7tY6d0RdHX/0HfPLRF991qTPO/OZ7+33fB+RDFjruKMWhcgwqwnYqaiVhKwXbiolRgZiuVxJ1LrsgEg25sFilFqrFBblQFXNVgajyBMCkxiuIs4yMMoiDSTiwoC3kc/hdVpdVPzFqRua9MpupS7mqmK9LVF0u1CVFzssCIXMYwKZcTGpMyWLUOe2yjoxAQyNO1YCutbV9f1tbe1u7dcRY47GaMiXuVVSFvMTjModLLApwaRefHqeibqNm4EBnb/+A1mo2T9kHcZ9tGR5LQocPvdFmNusEOqv8lPicxBESi4kstifzabeMuj1Wnba7i/Q71vDgJ/Xc4zXhyWrxq1pyG/eWvEMd+9umfWMLAiFWshKD/g8goROV5LjdBJa9ls9r2edr4svTjRenG8836o9WK7e46EXM5zbpNGoVX0qKdEqk00qFgkBnAGrOko+Nz1jA94iZhyvFZ8fZ707JLzaWft48/vJ08/6b5Y+K4azHph5QlbCIXE7yJYRXco8U4Ffvn7ZoAiPgRSp8vU59s9Z89s7RJ2fXtxerbxGpo5nIOWxaCLr1BhOZDUqlOF+ICyVEQSwnAcuBFmtHi8ekRT0OMjBZQyI3lvgduVCLzvGx0HJi9nw+WPTbJ4dNdSLMUTBXiAnF+GsSgKN7n62zxWnQVAIuOjCxEJnaxuevleMfkMjJzPxGcuZTPs4FoA0U3uHIIhHkqKgCr1CIAx6wBeptTY3ZTiRnzmPhbTp9gyFuc+hNBnm6Kj48tXy1Ri7Brs8WxTMVvITN8fkIT8I8GRXIKFCAR11g1wSo3WSIb9dXbojEnUb1C5m5XkJ+ePvI4/WVHSm3BE98SKNCZpbBg6xyntzz92QxN+U39XYBQDMdfbTWvCpiF/Kpyyy1Wy/fapS+XC5tcclq0JGdMOViHhYP/Sdzyn5+HuBR3+xwXwcABG1DNxvkOgqfLxR3q4vvoujFQvzeSuGymKV8w5C+m4p7GTTIYEE2F+b2iABcZnLW2tsNAPrOg5f4zJVyYgfH7vHibY76epm8eyR/BpuU0inPNBFwW6RcSPGVfQYPcbl5gM14Ex69/gDQvW8f7rHuyujtGnm3yT84Id45Sp8lw0dwZHPrqX0s3N/Xnwl56UyAI0KvCQN02lOIOcfV7erWFvBgOwwZj+VSm0LleDlfR+aOsfSDV3+yS1udHV2aAZNaBQ4aByddTjQ6VcGCAJv1wX472K9S96gOHeyZmkHoWJiJ+Jsxz9ax1Ve//33p3k86tVmnM2s0JgWtdlCrHdKDw5AVAoio+7DW2NOj6+lRazQj71/99crayY8b+P3rd3/5459Hv/017o7otENGo82gt5jNo0rq9cMGg0UPWoBZj7O3Dxzo1/f1alyT6OL645Xm2cvXflw+93Rz93tx6YJaZRocHDMYIKMRAg9bQHDYZIIMBrvZ7PwXcPlkRtKKCQsAAAAASUVORK5CYII=","width":50,"height":50,"src":"/static/dc9516b9327e627890a1f5c7f84c004a/45876/avatar.png","srcSet":"/static/dc9516b9327e627890a1f5c7f84c004a/45876/avatar.png 1x,\n/static/dc9516b9327e627890a1f5c7f84c004a/eb85b/avatar.png 1.5x,\n/static/dc9516b9327e627890a1f5c7f84c004a/4f71c/avatar.png 2x,\n/static/dc9516b9327e627890a1f5c7f84c004a/9ec3e/avatar.png 3x"}}},"headerImage":null},"html":"<p><strong>Intro</strong></p>\n<p>In this article, we will take a look at spring-batch and how it could be used. We will walk through various configurations and we will create an application which reads from a CSV file &#x26; writes into a database with outstanding performance.</p>\n<p>I used the following: Java 11, Spring 5+, Spring Boot 2 and Maven-based project.</p>\n<p><strong>Create a Project</strong></p>\n<p>First, we need to create a Spring Boot 2 project. We recommend that you do so by visiting <a href=\"https://start.spring.io/\">spring initializer</a>, which is a useful tool to generate spring projects with required dependencies and configurations.</p>\n<p><strong>Dependencies</strong></p>\n<p>We need the dependencies below to run and test the project:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">&lt;dependency> \n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter&lt;/artifactId>\n&lt;/dependency>\n   \n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n &lt;exclusions>\n    &lt;exclusion>\n     &lt;groupId>org.junit.vintage&lt;/groupId>\n      &lt;artifactId>junit-vintage-engine&lt;/artifactId>\n    &lt;/exclusion>\n &lt;/exclusions>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-batch&lt;/artifactId>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-data-jpa&lt;/artifactId>\n&lt;/dependency>    \n\n&lt;dependency>\n &lt;groupId>org.projectlombok&lt;/groupId>\n &lt;artifactId>lombok&lt;/artifactId>\n &lt;version>1.18.10&lt;/version>\n &lt;scope>provided&lt;/scope>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>com.h2database&lt;/groupId>\n &lt;artifactId>h2&lt;/artifactId>\n&lt;/dependency>\n    \n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n &lt;exclusions>\n     &lt;exclusion>\n     &lt;groupId>org.junit.vintage&lt;/groupId>\n     &lt;artifactId>junit-vintage-engine&lt;/artifactId>\n     &lt;/exclusion>\n &lt;/exclusions>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.batch&lt;/groupId>\n &lt;artifactId>spring-batch-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.hamcrest&lt;/groupId>\n &lt;artifactId>hamcrest-all&lt;/artifactId>\n &lt;version>1.3&lt;/version>\n &lt;scope>test&lt;/scope>\n&lt;/dependency></code></pre></div>\n<p><em>spring-boot-starter-batch</em> dependency includes all the configurations to run the spring batch application. <a href=\"https://projectlombok.org/\">Lombok</a>  is just a helper dependency to write the code faster and cleaner. <a href=\"http://www.h2database.com/\">H2</a>  is used as an in-memory database. <em>spring-boot-starter-test</em> and <em>spring-batch-test</em> are included for test purposes.</p>\n<p><strong>Book Class</strong></p>\n<p>Let’s create a model class book, which will represent a book. This will just serve as a model class and will help us during implementation of spring batch.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Data\n\npublic class Book {\nprivate String title;\nprivate String description;\nprivate String author;\n}</code></pre></div>\n<p><strong>Configuration</strong></p>\n<p>Let’s create a class called SpringBatchConfiguration. We will add all required configurations here. First, let’s annotate this class with <a href=\"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html\">@Configuration</a> to be able to inject beans, and with <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.html\">@EnableBatchProcessing</a> to enable spring batch processing. Additionally, we can add <a href=\"https://projectlombok.org/features/constructor\">@RequiredArgsConstructor</a> from Lombok which would help us to generate constructor with parameters. These parameters are the ones which are marked as final class properties. These properties will be injected as spring beans. Our class will be like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Configuration\n@EnableBatchProcessing\n@RequiredArgsConstructor\npublic class SpringBatchConfiguration</code></pre></div>\n<p>Now, in SpringBatchConfiguration class let’s add properties which need to be injected into the constructor:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">private final JobBuilderFactory jobBuilderFactory;\nprivate final StepBuilderFactory stepBuilderFactory;</code></pre></div>\n<p><em>jobBuilderFactory</em> and <em>stepBuilderFactory</em> are declared in spring batch jar as spring beans so that we can inject them in any class we want.</p>\n<p><strong>File Reader</strong></p>\n<p>Now, we need to declare and initialize spring beans to configure the batch process. The first bean which we will need will be responsible for reading from a file line by line. Spring Batch provides a default class for it. The class name is <em>FlatFileItemReader</em>. Similarly, spring has different default reader classes for reading from a relational database, mongodb and etc. However, if you need, you can create your own reader and implement it in a way you want.</p>\n<p>Let’s now see what <em>FlatFileItemReader</em> injection will look like.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\n@StepScope\npublic FlatFileItemReader&lt;Book> bookReader(@Value(\"#{jobParameters['filePath']}\") final String filePath\n) {\nreturn new FlatFileItemReaderBuilder&lt;Book>()\n    .name(\"personItemReader\")\n    .resource(new ClassPathResource(filePath))\n    .delimited()\n    .names(new String[]{\"title\", \"description\", \"author\"})\n    .fieldSetMapper(new BeanWrapperFieldSetMapper&lt;Book>() {{\n    setTargetType(Book.class);\n    }})\n    .build();\n}</code></pre></div>\n<p>We are configuring that the bean reader should read data from the given file path, which should be a CSV file having rows with title, description and author respectively.</p>\n<p>The <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/scope/StepScope.html\">StepScope</a> means to initialize this bean after each step, so file path could be dynamically set when the spring batch job is launched. We will come to that later, how to pass the file path later in this article.</p>\n<p><strong>Item Writer</strong></p>\n<p>Now, let’s create a writer class, which will take the data and write into the relational database.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic JdbcBatchItemWriter&lt;Book> writer(final DataSource dataSource) {\n    return new JdbcBatchItemWriterBuilder&lt;Book>()\n        .itemPreparedStatementSetter((book, preparedStatement) -> {\n            preparedStatement.setString(1, book.getTitle());\n            preparedStatement.setString(2, book.getDescription());\n            preparedStatement.setString(3, book.getAuthor());\n        })\n        .sql(\"INSERT INTO books (title, description, author) VALUES (title, description, author_surname)\")\n        .dataSource(dataSource)\n        .build();\n} </code></pre></div>\n<p>In the writer bean, we are setting item processors and adding values inside <em>preparedStatement</em> accordingly, which book property should be inserted for each db table column.  </p>\n<p><strong>Step Configuration</strong></p>\n<p>Now, let’s configure a step which will be executed in the batch process. In our case, the configuration will look like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic Step step1(final ItemWriter&lt;Book> writer, final ItemReader&lt;Book> reader) {\n    return stepBuilderFactory.get(\"step1\")\n        .&lt;Book, Book> chunk(100)\n        .reader(reader)\n        .processor((ItemProcessor&lt;Book, Book>) book -> book)\n        .writer(writer)\n        .build();\n}</code></pre></div>\n<p>In our scenario, we have only one step, but it's also possible to configure multiple steps. Here, we are creating a spring batch step with the name <em>step1</em> and setting reader and writer accordingly. These are the readers and writers which we created as spring beans earlier.</p>\n<p>We are setting chunk as 100, which means the items chunk proceeded is 100. We can make this configurable too. The processor is for converting the current object to the one which the writer should proceed with. In our case, it is the same object.</p>\n<p><strong>Job Configuration</strong></p>\n<p>Last but not least, let’s configure the job which should be executed.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic Job importUserJob(final Step step1) {\n    return jobBuilderFactory.get(\"bookReaderJob\")\n        .incrementer(new RunIdIncrementer())\n        .flow(step1)\n        .end()\n        .build();\n}</code></pre></div>\n<p>Here, we create a job with the name <em>bookReaderJob</em>. We add an incrementer <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/support/RunIdIncrementer.html\">RunIdIncrementer</a>, which is an id generator for the tables which are specifically designed to store the details about job executions in the database. After each time the spring batch job is executed, it will save details about the execution. To check the schema structure for storing this data, take a look at <a href=\"https://github.com/spring-projects/spring-batch/blob/master/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql\">this sql</a>. It is for MySql, but other SQL scripts are available too.</p>\n<p>Additionally, we add flow by which the job should be executed. Currently, we have only one step in our flow, so we add it.  </p>\n<p>Please also add this config: <code class=\"language-text\">spring.batch.job.enabled=false</code> in your properties config file so that spring batch job won’t be executed automatically with no parameters when the application is started.</p>\n<p><strong>Execution</strong></p>\n<p>To execute the job, we need to declare <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/JobLauncher.html\">JobLauncher</a> and launch the job. To do so, we need to create the below class:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Component\n@RequiredArgsConstructor\npublic class SpringBatchExecutor {  \n    private final JobLauncher jobLauncher;\n    private final Job job;  \n\n    @SneakyThrows\n    public void execute(final String filePath) {\n        JobParameters parameters = new JobParametersBuilder()\n            .addString(\"filePath\", filePath)\n            .toJobParameters();  \n        jobLauncher.run(job, parameters);\n    }  \n}</code></pre></div>\n<p>Now, we can call the execute method from whenever we want, with the file path from which the data should be read.</p>\n<p><strong>Additional Classes</strong></p>\n<p>There are a few additional classes which you will need to execute your code.</p>\n<p>Create a class <em>BookEntity</em> so that spring data JPA will automatically create the book table for you. Then you can create repository.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Data\n@Entity\n@Table(name = \"books\")\npublic class BookEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    private String title;\n    private String description;\n    private String authorFullName;\n}</code></pre></div>\n<p>Then create the interface <em>BookRepository</em> and extend it from <em>JpaRepository</em>. At the moment, we will need this only for testing.  </p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Repository\npublic interface BookRepository extends JpaRepository&lt;BookEntity, Long> {\n} </code></pre></div>\n<p><strong>Testing</strong></p>\n<p>No one likes a code which is not tested. So, let’s write a few test cases for our class.</p>\n<p>Here is a code sample for test cases:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Slf4j\n\n@SpringBootTest\nclass SpringBatchSampleApplicationIntegrationTest {\n\n    @Autowired\n    private SpringBatchExecutor springBatchExecutor;\n    \n    @Autowired\n    private BookRepository bookRepository;\n\n    @Test\n    public void testExecution() {\n        long initialCount = bookRepository.count();\n        assertThat(initialCount, equalTo(0L));\n        springBatchExecutor.execute(\"sample-data.csv\");\n        long count = bookRepository.count();\n        assertThat(count, equalTo(7L));\n    } \n\n    @Test\n    public void testLargeData() {\n        long startTime = System.currentTimeMillis();\n        long initialCount = bookRepository.count();\n        assertThat(initialCount, equalTo(0L));\n        springBatchExecutor.execute(\"large-data.csv\");\n        long count = bookRepository.count();\n        assertThat(count, equalTo(60000L));\n        long endTime = System.currentTimeMillis();\n        log.info(\"executed in miles: {}\", endTime - startTime);\n    }\n}</code></pre></div>\n<p>The second test executes in ≈ <strong>3500 ms</strong> in a machine with a 2.2 Ghz Intel Core i7 and 16GB ram. It proceeds 60K lines of CSV file, and saves it into a relational database.</p>\n<p>You can check out the working sample code in <a href=\"https://github.com/yolch-yolchyan/spring-batch-sample\">github</a>,</p>","fields":{"slug":"/spring-batch-integration/","tags":["auto1","engineering","spring","code","java"]}}},{"node":{"id":"a3a21c38-873e-56db-a170-eec0e6136040","frontmatter":{"category":"Coding","title":"Writing IntelliJ plugins","date":"2019-12-10","summary":"How to write a simple IntelliJ plugin","thumbnail":{"relativePath":"pages/intellij-plugin/logo.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAAEyElEQVQ4y3WVe0xTVxzHz723rfZBsVeqnc4Yw0aYFSiFFZlZxnSo4w+gSBWEglTmA59jCt4WCOUxq+J4zGwaMqfJfDBi2GbMxjJfMXMzJks0Y5ngfE0mqAwrFLHtPb+de4CKf+wkv5zfOed3Pr/vOb+Te9FAzEokNV9cLvq/Nmpew8L8DIQOANJUPEdh+wGpBB8jrSldo0hd7n0RLJry0fA4bCg2RxYw5S3zm/ISnsWt1vvi8qZMxD1KLkVvb+lmJifS7hrmpJ4lCfidAy8WAqZ8dqzPSwFzIQRN+UEy95SMbxK7MhK3umQiVlnuLVQKwxcVW//SS2O94JNFFZyja3NLHyD03JSHIN5OMxHQZ5C4Fgg0CAlFAIkOAMs6gIWbiV9sQ0cgTlMl+tW7ATSV+E64y2+R9kWVDXJR226PZZSAUvPG5mpxvP0+MfjjjfTg9eh0/Nv8TPGXBdmBS3H5cC52tS8253SffNMN0BZfHdVu7AK+tC9gIIon1MdsvkXvkKojd2aT1N02ZgXDWBnmGBbLJWM5IL4oZzhQEEOMTESsHBBiAjNzz8BrVSJEb721b3taOzdWlIQien+kECfB8gHciM8NkCHOyLLC711d+MPSUkzGIM159njE5IVJ1CfVgXnZbeKbpQ9Ey84+iCy7kxUqylB8QeQoUTeatAWumteIMhKs43m42dMDFotFAgDDMHD+/HlIT88YA7IsNmUcwWmbb4hRu+4BX9mfGQI+SSiY/SBxneeflJLHZ5NKQM7IqKofOzth1qxZMK4QOjo6CDCd+izLwNLlLXhh+R2sr3oEM4SHy8bUOYz07H0daFrQNWPw57XJQG8iIQEwxmC1WiE1NRWampqgu7sbzGYzBUqniLV9gee4B3GE6zHonAMpFAh7ZPSx+sqmH4Pa6XB5++vSHcJbixbhpsZGSEtLA6PRCK2trZCTkxNSK5m+oB2/4h6WYDjcOZgcOvLDHbxyZJfuT6ifDhc3zA8qZIqXNr5sTMiPsJ/CevcoEFgwzPnETGHPBB1VeG+TwQDV2rNnC98FBmlEhUKG1So1TJ06FeRyOchkMuA47iWgzv4t5msCQGB+ldNrDCns3b6AQgGOs42LS67PnR0J+z9pCDY3N+ODhw7i9RvW08qOW8jX2k/jaTUY1M6nI0rhaSSF9e2IRlXvXKD+paJrEbVL9varp6hBGx7m53lejNDxYrhKE6TKGAYj8tiJT4Ea+5mA1o1B5Rz2KwXvnJDClszDtNKfWw9bW7KOwrYl9bAltQGKF3sgb/k+WJpzAGIcXwYiHV/j2Y7vxAjHD4FwxznQurygqQdQunw9yrIBLYV5rEdRQ/YJCty74qujhwp/gt253/R/bP/+eJPj8vvr3JdWvld95X6Spxeiq//Gc2uegMEDoHP7Iaxy5ILKOVREjjtN2k+UIlS34hgF12UeVtRln/i02ta2aWN6q37ydy9mfQdvFK61zqu4CwZnbx8v9Ddrd95PnBxDVI455dknaE9A7OSA+lWn2Orsdi7zo05uYu5V513LTKHXMDHW12Gkcvk4pTBEi6qegLptJ2nfU9CJam1tXKWtjRFWtSND1S06b3H+yiRWXg8l1Ff8y5G3x6oELyJK0cwa6TfwmK79B0KrRX19cxThAAAAAElFTkSuQmCC","width":325,"height":325,"src":"/static/b2f976ed5fa752977d8f8f9b2516534c/b3029/logo.png","srcSet":"/static/b2f976ed5fa752977d8f8f9b2516534c/b3029/logo.png 1x,\n/static/b2f976ed5fa752977d8f8f9b2516534c/8d141/logo.png 1.5x,\n/static/b2f976ed5fa752977d8f8f9b2516534c/ee72c/logo.png 2x,\n/static/b2f976ed5fa752977d8f8f9b2516534c/5dfa8/logo.png 3x"}}},"authorName":"Piotr Czekaj","authorDescription":"Piotr is a Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/intellij-plugin/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACyElEQVQ4y4VUO2gUURR9mZmN8YcItoKVjeXuzsxu9jOzm92NkBBTqDEmjZAEgtqIpSgI1gp+uhS6kEI7wc7GQsFCLERiISGCaGHhF02yu+M5s/cujyC4cJY379573r3n3XdNvV51xsYiJ4rKbqlUyBj8wjAYyWazp4BVYB3YAraBDeAhMBcE/l76lkrFTLVachuNyCGXEbJMrVbx6ADnaWAtn88nvu8nWBMdQcJ9Auv3wCxjWq2aC9IMSF3DzCyyG1YAs9pSYkL2melmLpfT71tKGkUlz2iZJAuCgA5dEjGAwPoV0AbuAy+tfR7YkZjb5CiXR1OutEw5jWQdCfgCnEiSxNg/7LWAj+KTSiHSzBvrAtascrriPCmnDkP4AYQ0Em3TGPHf8P38ARpP/4PsHQ7aU69Hqdi4OJeI47JXLg9IX1tVbUuWCzSsygcJO+L0goGNRpySoB1colarerhEvcCn4tuxCB/TsC5ZdS3Ct2Ho75uZmRoigWbIbgCxt7Q0O8TLsgg17o2R2+JHzzJcV/1IoBlybel42SLsCcc3I6UqoaZ+lUHFYjjCzJSQa+4J4UVLKiX8ScOGVXJXDN+xd7SfZXFYS65U+n2Wz+cOw+ezHadSkfCRdVJiZdlmMG9ZtHMpgWR3Z0eMSvWExjmrbXoCbZ951dIim7Yk6u1IYtlwavChW51Pp01tXOwf01eC9RHs/Rbbnx2Hf4IUh/Q5zdol8H3KG22Pj9edOK54LF1876pd37TELhqdZ+J4MwxDHUvXgDOFQrB7YqLpjI4WdhG8GLYNbCeBK/1+TWNWRG+Pfy5Hj5Dek3IXzX9+8DkL/AIeLC/PDU1NHXdSQk5altNsDkjPAT+ArzKyFnK5bB2IxbYik4hanmfM5GTL6Q/Y2DUc25y0HLToubR8TI39cL4APJOxr/35AXgOXEJnHKQvepP6eiQj119kDrZjqLo8HAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png","srcSet":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png 1x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/eb85b/avatar.png 1.5x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/4f71c/avatar.png 2x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/9ec3e/avatar.png 3x"}}},"headerImage":{"relativePath":"pages/intellij-plugin/header.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAADGklEQVQozx3R60/bVRjA8V80m2N4IVkWcfpmqyulTdd7oRYopEDbyaXlVgptobANkAzrUpwbyjZvgMpQ7CxhGCaXUTcv04mRRYMkKpB1MLYpIKkymb5S/4WvZ7745DkvnnOey5FS87qQWbqY0zWyIEzrmxkxHOFHcXZkRZCsZ0ixnmKH9TQPWV7m0eIo22q/JmjvI6ENsijyh7I62G7rYXt2F1KgpIo2TZhlbYBFkTCjDaE2Rdhn6mS36TiPC+nml9ilP0a6rQ91wxwZhxJ4ar9kMbuDdeMR+h0DyAOzyBwxpJDhMCsHAlzXBFgQ7mj8DGqaeUr3Agrt8yh0YWQiZolOq9wTuLxXcFZ/SmHNFVrKL/DKwXMcrPkcu2+aYkGKqkKsquu5rvZzQ7gf19R1nFTUo1M0Y5E3YNWGidu6mSzsQdc0j+ZwgrzgLFUVcWEKn/uiMInfPY40pPKxrvWypPFyU7ilqWZJX8dr9l4UrjFUzlFy3OJS8BqWtmVkRzeQtf/K3qNJCsT4EWeUU65+ely9HHOdQ3pR4Wa1OI8Vez637DaWiwp5tfxtIidmGbywxsilJH2xn3lneI3RyXU+iK3Q1votPu9VfP5vOOGMEbeHmXEcoregV+wwvYx4volk816SoSeZrS3lfPmbfFj2OqPeQcbqo4z7+pnwDvBxYJjJ2iFGSt8j5jhLzPUuY7ndXLREmMruZMJyHCmyq4jO3UWMGg1MO+XM5z7LkiHEDwcamVM28JM6wFpODas5Xm7omljRt3DH1Mptcxu/GFv4yNlNZ3CQPk8P14ytSG+l5XHmESund+ZzcmcuX+xx8ldmNckMD78pKthUevjHXMK/5lLuZlYIlWwpq9lSVJJUeQkFL2N77ntsHfO8URpFGk41M572DLFUI++nGDj/sJnb+0q4l1nFH8LW/yrZ2O8mKfdwVxT5/X4x4c8MN+3ip1XtGxj8X+GouYw081gW9+TlfJaiY/xBJWMPKPlkh5bEEwXc3GMnkV7Ad2nZxLepuZpqYHN/GVvisU3h76dLmDKGsTQuoK+7RJNrgP8AU8rtTZmilCkAAAAASUVORK5CYII=","width":1280,"height":720,"src":"/static/6fae10ea7503a11a8f308ac2e2a5217a/26421/header.png","srcSet":"/static/6fae10ea7503a11a8f308ac2e2a5217a/26421/header.png 1x"}}}},"html":"<h2>Background</h2>\n<p>During analysis of why some services take a long time to build, several problems have been found, mostly related to number of Spring contexts that were created during integration tests. More about those problems can be found in <a href=\"https://auto1.tech/integration-test-speedup\">previous blog post</a>. In order to decrease the likelihood that slow tests will appear at AUTO1, we implemented an IntelliJ plugin which warns a developer about possible problems when he writes code and provides quick fixes if they are applicable.</p>\n<h2>Plugin inspections</h2>\n<ul>\n<li>warns about usage of <code class=\"language-text\">@DirtiesContext</code> annotation which slows tests down because it forces creation of new Spring context</li>\n<li>warns about specifying the same list of profiles but in different order in <code class=\"language-text\">@ActiveProfiles</code> (slows tests down because different Spring context would have to be created for each combination)</li>\n<li>warns if number of distinct profile combinations used in <code class=\"language-text\">@ActiveProfiles</code> is greater than 3</li>\n<li>warns if it finds <code class=\"language-text\">@FeignClient</code> (or DTO used by client) not documented with Swagger annotations</li>\n<li>warns if remote HTTP call is used inside method annotated with <code class=\"language-text\">@Transactional</code></li>\n</ul>\n<p>During the plugin development, we realized that resources on writing IntelliJ plugins are scarce, and therefore decided to create a step by step tutorial on how to implement <code class=\"language-text\">@DirtiesContext</code> inspection.</p>\n<h2>Create plugin</h2>\n<p>In our tutorial we will be using the community edition of IntelliJ 2018.3.5. Final source code is available on <a href=\"https://github.com/pczekaj/tutorial-intellij-plugin\">github</a>.\nThere are several ways on how to create IntelliJ plugin. The recommended one is to use Intellij plugin for Gradle and that’s what we will use in this tutorial. Let’s start with creating new project using <em>File > New > Project...</em>, select Gradle and make sure that in <em>Additional Libraries and Frameworks</em> both <em>Java</em> and <em>Intellij Platform Plugin</em> are selected.\nIf you cannot see “Gradle” or “Intellij Platform Plugin” then please make sure that both “Gradle” and “Plugin DevKit” IntelliJ plugins are installed.\nContinue next steps of the wizard using default values, use any groupId and artifactId. After some time IntelliJ will download dependencies and create an empty plugin.</p>\n<h2>Inspection</h2>\n<p>Our inspection should warn a developer each time there is a class annotated with <code class=\"language-text\">org.springframework.test.annotation.DirtiesContext</code> and provide quick fix which deletes <code class=\"language-text\">@DirtiesContext</code>. If Spring is not your thing then any other annotation can be used instead.</p>\n<p>There are two kinds of inspections:</p>\n<p>Local inspections are executed in the background when file is opened, in general they have access to currently open file. Local inspection cannot report problem for not currently processed file. Inspection class has to extend <code class=\"language-text\">com.intellij.codeInspection.LocalInspectionTool</code> or one of subclasses.\nGlobal inspections work only in batch mode when analysis is manually triggered via <em>Analyze > Inspect Code</em> and see complete graph of references between classes and can report problem for any file. Inspection class has to extend <code class=\"language-text\">com.intellij.codeInspection.GlobalInspectionTool</code> or one of subclasses.</p>\n<p>We don’t need to access complete graph of references and we would like to give a hint that something is wrong as soon as possible so local inspection is a better choice for our use case. First problem that we encounter is to pick proper base class. One approach is to ask IntelliJ to show class Hierarchy of <code class=\"language-text\">LocalInspectionTool</code> and take a look what other inspections are extending. In this case <code class=\"language-text\">AbstractBaseJavaLocalInspectionTool</code> seems to be a good choice since most of Java inspections are based on it.</p>\n<p>Create new class named <code class=\"language-text\">DirtiesContextInspection</code> with following content:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">DirtiesContextFirstVersionInspection</span> <span class=\"token keyword\">extends</span> <span class=\"token class-name\">AbstractBaseJavaLocalInspectionTool</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">final</span> <span class=\"token class-name\">String</span> DIRTIES_CONTEXT <span class=\"token operator\">=</span> <span class=\"token string\">\"org.springframework.test.annotation.DirtiesContext\"</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">final</span> <span class=\"token class-name\">String</span> DESCRIPTION_TEMPLATE <span class=\"token operator\">=</span> <span class=\"token string\">\"Usage of @DirtiesContext makes integration tests slower\"</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getDisplayName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token string\">\"Usage of @DirtiesContext is not recommended\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getGroupDisplayName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token class-name\">GroupNames</span><span class=\"token punctuation\">.</span>PERFORMANCE_GROUP_NAME<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getShortName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token string\">\"DirtiesContext\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">boolean</span> <span class=\"token function\">isEnabledByDefault</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token class-name\">PsiElementVisitor</span> <span class=\"token function\">buildVisitor</span><span class=\"token punctuation\">(</span><span class=\"token annotation punctuation\">@NotNull</span> <span class=\"token keyword\">final</span> <span class=\"token class-name\">ProblemsHolder</span> holder<span class=\"token punctuation\">,</span> \n                                          <span class=\"token keyword\">boolean</span> isOnTheFly<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">JavaElementVisitor</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\n            <span class=\"token keyword\">public</span> <span class=\"token keyword\">void</span> <span class=\"token function\">visitAnnotation</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">PsiAnnotation</span> annotation<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">super</span><span class=\"token punctuation\">.</span><span class=\"token function\">visitAnnotation</span><span class=\"token punctuation\">(</span>annotation<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n                <span class=\"token class-name\">String</span> qualifiedName <span class=\"token operator\">=</span> annotation<span class=\"token punctuation\">.</span><span class=\"token function\">getQualifiedName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n                <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>DIRTIES_CONTEXT<span class=\"token punctuation\">.</span><span class=\"token function\">equals</span><span class=\"token punctuation\">(</span>qualifiedName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n                    holder<span class=\"token punctuation\">.</span><span class=\"token function\">registerProblem</span><span class=\"token punctuation\">(</span>annotation<span class=\"token punctuation\">,</span> DESCRIPTION_TEMPLATE<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n                <span class=\"token punctuation\">}</span>\n            <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>The most interesting thing happens in a visitor which is notified each time a Java annotation is encountered in source code. By overriding different methods, plugin can process methods, classes, imports etc. In our case we check if fully qualified name of annotation matches our expectations and register a problem when that’s the case. Later on we will change this class to include also a quick fix. Creation of inspection class is not enough to make it available in IntelliJ - it’s also needed to register inspection in <code class=\"language-text\">plugin.xml</code> which among other things describes what plugin does, what other plugins are required and in which version of IntelliJ it can be used.</p>\n<p>It’s possible to register each inspection one by one in <code class=\"language-text\">plugin.xml</code> under extensions tag and configure inspection using xml but we find it easier to register <code class=\"language-text\">inspectionToolProvider</code> and configure using it in Java code.</p>\n<p>Create the following class to implement our inspection provider:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">CodeInspectionProvider</span> <span class=\"token keyword\">implements</span> <span class=\"token class-name\">InspectionToolProvider</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token class-name\">Class</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token function\">getInspectionClasses</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Class</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">{</span>\n                <span class=\"token class-name\">DirtiesContextInspection</span><span class=\"token punctuation\">.</span><span class=\"token keyword\">class</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>And register it in <code class=\"language-text\">plugin.xml</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"xml\"><pre class=\"language-xml\"><code class=\"language-xml\">   <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>extensions</span> <span class=\"token attr-name\">defaultExtensionNs</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.intellij<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>inspectionToolProvider</span> <span class=\"token attr-name\">implementation</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>com.auto1.intellij.tutorial.CodeInspectionProvider<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">/></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>extensions</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>Now it’s time to check our inspection in action by running runIde gradle task. This could be done from terminal by executing <code class=\"language-text\">./gradlew runIde</code> but it’s better to use Gradle view in IntelliJ since it allows to start IDE in debug mode (if needed just right click on task and select Debug, you can create run configuration to speed-up in the future). New IntelliJ instance should show up, if you already have sources of some project that uses Spring Boot then open it, otherwise you could create a new project. Annotate some class with <code class=\"language-text\">@DirtiesContext</code> annotation and observe inspection marker showing up. In case of problems logs can be found at <code class=\"language-text\">build/idea-sandbox/system/log/</code>.</p>\n<h2>Adding quick fix</h2>\n<p>Many inspections report not only problems but also provide automatic ways of fixing issues. In case of DirtiesContext, it’s not possible to provide safe way of removing it because DirtiesContext is often used when bean holds some state which makes tests dependent on each other. Usually we want to remove the annotation and then figure out the \"dirty parts\" and clean them up in an elegant way. Since the second part is hard to automate we will provide quick fix which only removes annotation.</p>\n<p>Go back to <code class=\"language-text\">DirtiesContextInspection</code> class and add quick fix:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">   <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">DeleteQuickFix</span> <span class=\"token keyword\">implements</span> <span class=\"token class-name\">LocalQuickFix</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">public</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> <span class=\"token string\">\"Removes usage of @DirtiesContext\"</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token keyword\">void</span> <span class=\"token function\">applyFix</span><span class=\"token punctuation\">(</span><span class=\"token annotation punctuation\">@NotNull</span> <span class=\"token class-name\">Project</span> project<span class=\"token punctuation\">,</span> <span class=\"token annotation punctuation\">@NotNull</span> <span class=\"token class-name\">ProblemDescriptor</span> descriptor<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            descriptor<span class=\"token punctuation\">.</span><span class=\"token function\">getPsiElement</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">delete</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n\n        <span class=\"token keyword\">public</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getFamilyName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">return</span> <span class=\"token function\">getName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<p>Next step is to pass quick fix when problem is registered:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">holder<span class=\"token punctuation\">.</span><span class=\"token function\">registerProblem</span><span class=\"token punctuation\">(</span>annotation<span class=\"token punctuation\">,</span> DESCRIPTION_TEMPLATE<span class=\"token punctuation\">,</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">DeleteQuickFix</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Executing <code class=\"language-text\">runIde</code> Gradle task should prove that quick fix works as expected. </p>\n<h2>Internationalization</h2>\n<p>Up to this point we have used hardcoded strings inside inspection name and description. To allow the plugin to be accessible in different languages we can externalize the messages. For this purpose we can use properties file. First create <code class=\"language-text\">PluginBundle</code> class:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">PluginBundle</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token class-name\">Reference</span><span class=\"token generics\"><span class=\"token punctuation\">&lt;</span><span class=\"token class-name\">ResourceBundle</span><span class=\"token punctuation\">></span></span> ourBundle<span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token keyword\">final</span> <span class=\"token class-name\">String</span> BUNDLE <span class=\"token operator\">=</span> <span class=\"token string\">\"com.auto1.intellij.tutorial.PluginBundle\"</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">private</span> <span class=\"token class-name\">PluginBundle</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">static</span> <span class=\"token class-name\">String</span> <span class=\"token function\">message</span><span class=\"token punctuation\">(</span><span class=\"token annotation punctuation\">@NotNull</span> <span class=\"token annotation punctuation\">@PropertyKey</span><span class=\"token punctuation\">(</span>resourceBundle <span class=\"token operator\">=</span> BUNDLE<span class=\"token punctuation\">)</span> <span class=\"token class-name\">String</span> key<span class=\"token punctuation\">,</span> \n                                 <span class=\"token annotation punctuation\">@NotNull</span> <span class=\"token class-name\">Object</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span> params<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token class-name\">CommonBundle</span><span class=\"token punctuation\">.</span><span class=\"token function\">message</span><span class=\"token punctuation\">(</span><span class=\"token function\">getBundle</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> key<span class=\"token punctuation\">,</span> params<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">private</span> <span class=\"token keyword\">static</span> <span class=\"token class-name\">ResourceBundle</span> <span class=\"token function\">getBundle</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token class-name\">ResourceBundle</span> bundle <span class=\"token operator\">=</span> <span class=\"token class-name\"><span class=\"token namespace\">com<span class=\"token punctuation\">.</span>intellij<span class=\"token punctuation\">.</span>reference<span class=\"token punctuation\">.</span></span>SoftReference</span><span class=\"token punctuation\">.</span><span class=\"token function\">dereference</span><span class=\"token punctuation\">(</span>ourBundle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>bundle <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            bundle <span class=\"token operator\">=</span> <span class=\"token class-name\">ResourceBundle</span><span class=\"token punctuation\">.</span><span class=\"token function\">getBundle</span><span class=\"token punctuation\">(</span>BUNDLE<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            ourBundle <span class=\"token operator\">=</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">SoftReference</span><span class=\"token generics\"><span class=\"token punctuation\">&lt;</span><span class=\"token punctuation\">></span></span><span class=\"token punctuation\">(</span>bundle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token keyword\">return</span> bundle<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Then create a new file matching <code class=\"language-text\">BUNDLE</code> constant, in my case it will be <code class=\"language-text\">src/main/resources/com/auto1/intellij/tutorial/PluginBundle.properties</code> with content:</p>\n<div class=\"gatsby-highlight\" data-language=\"properties\"><pre class=\"language-properties\"><code class=\"language-properties\"><span class=\"token attr-name\">inspection.dirties.context.display.name</span><span class=\"token punctuation\">=</span><span class=\"token attr-value\">Usage of @DirtiesContext is not recommended</span>\n<span class=\"token attr-name\">inspection.dirties.context.problem.descriptor</span><span class=\"token punctuation\">=</span><span class=\"token attr-value\">Usage of @DirtiesContext makes integration tests slower</span>\n<span class=\"token attr-name\">inspection.dirties.context.use.quickfix</span><span class=\"token punctuation\">=</span><span class=\"token attr-value\">Removes usage of @DirtiesContext</span></code></pre></div>\n<p>Register bundle in <code class=\"language-text\">plugin.xml</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"xml\"><pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>resource-bundle</span><span class=\"token punctuation\">></span></span>com.auto1.intellij.tutorial.PluginBundle<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>resource-bundle</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>And finally use bundle in inspection, for example:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">    <span class=\"token keyword\">public</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getDisplayName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token class-name\">PluginBundle</span><span class=\"token punctuation\">.</span><span class=\"token function\">message</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"inspection.dirties.context.display.name\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<h2>Testing</h2>\n<p>We picked <code class=\"language-text\">LightPlatformCodeInsightFixtureTestCase</code> as base for our tests because it is recommended in the documentation. Unfortunately, testing appeared harder to set up properly than expected.</p>\n<p>First problem was that our tests couldn’t see classes from JDK, which was fixed by specifying project descriptor to use internal JDK: </p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">    <span class=\"token keyword\">protected</span> <span class=\"token class-name\">LightProjectDescriptor</span> <span class=\"token function\">getProjectDescriptor</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">LightProjectDescriptor</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">public</span> <span class=\"token class-name\">Sdk</span> <span class=\"token function\">getSdk</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n                <span class=\"token keyword\">return</span> <span class=\"token class-name\">JavaAwareProjectJdkTableImpl</span><span class=\"token punctuation\">.</span><span class=\"token function\">getInstanceEx</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getInternalJdk</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token punctuation\">}</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<p>Second problem was that visitor received incorrect fully qualified class name of the annotation. Instead of <code class=\"language-text\">org.springframework.test.annotation.DirtiesContext</code>, it got <code class=\"language-text\">DirtiesContext</code> while it worked fine for real project in IDE. It turns out that such behaviour occurs when test project doesn’t see definition of some class. This is fixable by either hardcoding problematic class into test or by adding dependency as library to the project. Second approach avoids copying source code from other projects and seems to be more interesting so it will be presented here. In order to download dependency jar we use <code class=\"language-text\">ShrinkWrap</code> library:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">private</span> <span class=\"token class-name\">File</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token function\">getMavenArtifacts</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">String</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span> mavenArtifacts<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token class-name\">File</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> files <span class=\"token operator\">=</span> <span class=\"token class-name\">Maven</span><span class=\"token punctuation\">.</span><span class=\"token function\">resolver</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n                            <span class=\"token punctuation\">.</span><span class=\"token function\">resolve</span><span class=\"token punctuation\">(</span>mavenArtifacts<span class=\"token punctuation\">)</span>\n                            <span class=\"token punctuation\">.</span><span class=\"token function\">withoutTransitivity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n                            <span class=\"token punctuation\">.</span><span class=\"token function\">asFile</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>files<span class=\"token punctuation\">.</span>length <span class=\"token operator\">==</span> <span class=\"token number\">0</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">IllegalArgumentException</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"Failed to resolve artifacts \"</span> <span class=\"token operator\">+</span> <span class=\"token class-name\">Arrays</span><span class=\"token punctuation\">.</span><span class=\"token function\">toString</span><span class=\"token punctuation\">(</span>mavenArtifacts<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token keyword\">return</span> files<span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<p>When dependency is resolved and downloaded into local Maven cache it can be added as library with code listed below:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">   <span class=\"token keyword\">protected</span> <span class=\"token keyword\">void</span> <span class=\"token function\">attachMavenLibrary</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">String</span> mavenArtifact<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token class-name\">File</span><span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> jars <span class=\"token operator\">=</span> <span class=\"token function\">getMavenArtifacts</span><span class=\"token punctuation\">(</span>mavenArtifact<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token class-name\">Arrays</span><span class=\"token punctuation\">.</span><span class=\"token function\">stream</span><span class=\"token punctuation\">(</span>jars<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span>jar <span class=\"token operator\">-></span> <span class=\"token punctuation\">{</span>\n            <span class=\"token class-name\">String</span> name <span class=\"token operator\">=</span> jar<span class=\"token punctuation\">.</span><span class=\"token function\">getName</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token class-name\">PsiTestUtil</span><span class=\"token punctuation\">.</span><span class=\"token function\">addLibrary</span><span class=\"token punctuation\">(</span>myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">getProjectDisposable</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> myModule<span class=\"token punctuation\">,</span> name<span class=\"token punctuation\">,</span> jar<span class=\"token punctuation\">.</span><span class=\"token function\">getParent</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> name<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<p>It’s important to use <code class=\"language-text\">myFixture.getProjectDisposable()</code> instead of <code class=\"language-text\">myFixture.getProject()</code> otherwise there is an exception during test shutdown:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">com.intellij.openapi.util.TraceableDisposable$DisposalException: Virtual pointer 'jar:///somePath/.m2/repository/org/springframework/spring-test/5.1.5.RELEASE/spring-test-5.1.5.RELEASE.jar!/' hasn't been disposed</code></pre></div>\n<p>Next surprise is that by default test searches for test data in strange location inside IntelliJ home folder which can be fixed with overriding <code class=\"language-text\">getTestDataPath</code>. Since input files most likely won’t compile because of missing imports and possible usage of special markers like <em><caret></em> we don’t use <code class=\"language-text\">src/test/java</code> folder to store them:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">    <span class=\"token annotation punctuation\">@Override</span>\n    <span class=\"token keyword\">protected</span> <span class=\"token class-name\">String</span> <span class=\"token function\">getTestDataPath</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">return</span> <span class=\"token string\">\"src/test/testData\"</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<p>Inspection tests can be done by providing file to analyse and resulting file that should be created after given quick fix has been applied. Inspection tests use <code class=\"language-text\">configureByFile</code> to load input file, <code class=\"language-text\">doHighlighting</code> to trigger source code analysis, launchAction to execute quick fix and finally <code class=\"language-text\">checkResultByFile</code> to compare results against <em>after</em> file.</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\">    myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">configureByFile</span><span class=\"token punctuation\">(</span>testName <span class=\"token operator\">+</span> <span class=\"token string\">\".java\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">enableInspections</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">new</span> <span class=\"token class-name\">DirtiesContextInspection</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">doHighlighting</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token class-name\">IntentionAction</span> quickFixAction <span class=\"token operator\">=</span> myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">findSingleIntention</span><span class=\"token punctuation\">(</span>intentionHint<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">launchAction</span><span class=\"token punctuation\">(</span>quickFixAction<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    myFixture<span class=\"token punctuation\">.</span><span class=\"token function\">checkResultByFile</span><span class=\"token punctuation\">(</span>testName <span class=\"token operator\">+</span> <span class=\"token string\">\".after.java\"</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Complete source code can be found at github <a href=\"https://github.com/pczekaj/tutorial-intellij-plugin\">github</a></p>\n<h2>Next steps</h2>\n<p>If you would like to share your plugin with other developers, you can publish to JetBrains plugin repository as described in the documentation. Other simple option is to execute buildPlugin Gradle task which will create plugin zip file inside build/distributions and then install it via <em>Install Plugin from disk...</em> available inside <em>Preferences > Plugins</em> (in IntelliJ 2018.3 is available through \"gears icon\").</p>\n<p>When working on your own ideas you might run into a situation when you don't know how to implement some functionality. In this situation you could try to find the answer using links provided in section below. What also worked for us was reading source code of inspections available as part of community edition of IntelliJ, often there is an existing inspection which does a similar thing to what you might want to do.</p>\n<h2>Links</h2>\n<ul>\n<li><a href=\"http://www.jetbrains.org/intellij/sdk/docs/faq.html\">IntelliJ SDK FAQ</a></li>\n<li><a href=\"https://github.com/JetBrains/intellij-sdk-docs/blob/master/tutorials/code_inspections.md\">Code inspections tutorial</a></li>\n<li><a href=\"https://intellij-support.jetbrains.com/hc/en-us/community/topics/200366979-IntelliJ-IDEA-Open-API-and-Plugin-Development\">Plugin development forum</a></li>\n<li><a href=\"https://github.com/JetBrains/intellij-community\">Source code of IntelliJ community edition</a></li>\n<li><a href=\"https://github.com/JetBrains/intellij-plugins\">Source code of many IntelliJ plugins</a></li>\n</ul>","fields":{"slug":"/intellij-plugin/","tags":["intellij"]}}},{"node":{"id":"df094608-f958-5bcd-b406-ea359067fbd8","frontmatter":{"category":"Coding","title":"PostgreSQL 12 - a precious release","date":"2019-10-29","summary":"Improved Common Table Expressions in recent release of Postgres","thumbnail":{"relativePath":"pages/postgres12-a-precious-release/elephant_cropped.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAWABQDASIAAhEBAxEB/8QAGQABAQADAQAAAAAAAAAAAAAAAAMBAgQF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQIA/9oADAMBAAIQAxAAAAHuzwyh9NJUx1M1Cf/EABoQAAIDAQEAAAAAAAAAAAAAAAEDABESAhD/2gAIAQEAAQUC9ZwwnDAsaq5ZgM//xAAYEQADAQEAAAAAAAAAAAAAAAAAARICE//aAAgBAwEBPwGDmxaZbP/EABcRAAMBAAAAAAAAAAAAAAAAAAACEwH/2gAIAQIBAT8BoVU1MJqf/8QAGxAAAgEFAAAAAAAAAAAAAAAAADEBECAhQWH/2gAIAQEABj8CrPIwNM1Z/8QAHBABAAICAwEAAAAAAAAAAAAAAQARITEQQVFx/9oACAEBAAE/IVDbLPeEWs5BNbatDthqyfsUeMAiBon/2gAMAwEAAgADAAAAEL/Xfv/EABkRAQEAAwEAAAAAAAAAAAAAAAEAMUFRYf/aAAgBAwEBPxAOy4kkzet//8QAGREAAwADAAAAAAAAAAAAAAAAAAExEVFh/9oACAECAQE/EHnGb2IuHE//xAAdEAEAAgICAwAAAAAAAAAAAAABABEhMRBRYXGh/9oACAEBAAE/EFAAVogmhwCxZXVnre/cLABvFBlrzTMCB1e/kSbKO4RYmd3m4ZIf/9k=","width":292,"height":325,"src":"/static/5076cf686ef8d2a21a3a6da9bf016970/a2998/elephant_cropped.jpg","srcSet":"/static/5076cf686ef8d2a21a3a6da9bf016970/a2998/elephant_cropped.jpg 1x,\n/static/5076cf686ef8d2a21a3a6da9bf016970/99cce/elephant_cropped.jpg 1.5x,\n/static/5076cf686ef8d2a21a3a6da9bf016970/6e995/elephant_cropped.jpg 2x,\n/static/5076cf686ef8d2a21a3a6da9bf016970/968b1/elephant_cropped.jpg 3x"}}},"authorName":"Mariusz Nowak","authorDescription":"Mariusz is a Senior Software Engineer at AUTO1 Group.","authorAvatar":null,"headerImage":null},"html":"<p>The PostgreSQL team <a href=\"https://www.postgresql.org/about/news/1976/\">announced recently</a> a new release of the most advanced\nopen source relational database - PostgreSQL 12. As usual it comes with an impressive list of improvements\n(generated columns ♥️), one of them being long awaited by dozens of developers: <strong>improved Common Table Expressions</strong>.</p>\n<p>I should first explain what are the Common Table Expressions for those who are unfamiliar with them:\nthe CTE’s, often called <code class=\"language-text\">“WITH queries”</code>, are SQL constructs giving a possibility of creating <strong>temporal data views</strong> for a sake of a query execution.</p>\n<p>Essentially CTE is an additional query which results can be referenced in the subsequent CTE’s or the main query before which\nit is being placed. It should be clear enough with an example - here’s a sample query with two CTE’s taken from Postgres docs:</p>\n<div class=\"gatsby-highlight\" data-language=\"sql\"><pre class=\"language-sql\"><code class=\"language-sql\"><span class=\"token keyword\">WITH</span> regional_sales <span class=\"token keyword\">AS</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token keyword\">SELECT</span> region<span class=\"token punctuation\">,</span> <span class=\"token function\">SUM</span><span class=\"token punctuation\">(</span>amount<span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> total_sales\n    <span class=\"token keyword\">FROM</span> orders\n    <span class=\"token keyword\">GROUP</span> <span class=\"token keyword\">BY</span> region\n<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> top_regions <span class=\"token keyword\">AS</span> <span class=\"token punctuation\">(</span>\n    <span class=\"token keyword\">SELECT</span> region\n    <span class=\"token keyword\">FROM</span> regional_sales\n    <span class=\"token keyword\">WHERE</span> total_sales <span class=\"token operator\">></span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">SELECT</span> <span class=\"token function\">SUM</span><span class=\"token punctuation\">(</span>total_sales<span class=\"token punctuation\">)</span><span class=\"token operator\">/</span><span class=\"token number\">10</span> <span class=\"token keyword\">FROM</span> regional_sales<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">SELECT</span> region<span class=\"token punctuation\">,</span>\n       product<span class=\"token punctuation\">,</span>\n       <span class=\"token function\">SUM</span><span class=\"token punctuation\">(</span>quantity<span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> product_units<span class=\"token punctuation\">,</span>\n       <span class=\"token function\">SUM</span><span class=\"token punctuation\">(</span>amount<span class=\"token punctuation\">)</span> <span class=\"token keyword\">AS</span> product_sales\n<span class=\"token keyword\">FROM</span> orders\n<span class=\"token keyword\">WHERE</span> region <span class=\"token operator\">IN</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">SELECT</span> region <span class=\"token keyword\">FROM</span> top_regions<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">GROUP</span> <span class=\"token keyword\">BY</span> region<span class=\"token punctuation\">,</span> product<span class=\"token punctuation\">;</span></code></pre></div>\n<p>Now we know what they are, but what purpose can they serve us? Well - we could parry here and say: for the same purpose as ordinary database views serve.\nThat’s of course a dramatic simplification - CTE’s are much more powerful and should not be treated as a simple database views.\nNevertheless I would like to keep the collation for the sake of this article.</p>\n<p>Database views are absolutely optional - one can simply substitute them with a subquery and achieve identical results.\nIndeed that’s what modern database engines do these days - once a database view is being used they inline it’s query as a subquery.\nWhy to bother then? We are able to deal completely without database views and even if we did use one the database engine would get rid of it anyway.</p>\n<p>What benefits do views give us then? And why the heck are they inlined?</p>\n<h3>The beauty of database views</h3>\n<p>The most appropriate explanation here is that database views help us achieve better readability.\nThey offer an elegant way of abstracting some parts of a database into a meaningful object, often matching closely with the domain.</p>\n<p>Instead of creating giant and ugly looking queries it is possible to extract some of its parts into an appealing view which is easier to browse and select data from. <strong>\"Divide and conquer\"</strong> rule in it’s true form.</p>\n<p>Still, we didn’t answer the fact that the underlying view’s query is most of the times <strong>inlined</strong> while it is being referenced. The reason is <strong>performance</strong> of course. Smart guys found out that lazy evaluation helps the optimizer a lot - by delaying the execution we could take advantage of a context of the actual query.</p>\n<p>This in turn allows many clever optimization techniques, like: pushing down predicates (<em>WHERE filters</em>), eliminating unnecessary <em>JOINS</em>, accessing only subset of columns etc.\nIn other words - a database is smart enough to do as little work as possible when evaluating database views, in the context of the issued query.</p>\n<p>Personally I love this pattern: aggregating all the data into views and letting a database to optimize my queries - these folks are really good in it and my queries are dead simple too.</p>\n<p>I have mentioned the CTE’s at the beginning, saying they are able to create temporal data views. I still conform to the comparison with database views - they both are in many cases similar.\nThe main difference is that CTE results are temporary and are reachable only in the context of a query which CTE is being part of.</p>\n<p>It may make sense to use a CTE in a place where a regular database view is not justified (e.g. it makes sense only in the context of a query and not in the whole domain), expecting similar behavior.</p>\n<h3>An ugly brother</h3>\n<p>Besides many remarkable advantages there’s at least one disadvantage which disqualifies CTE's for most use cases - before PostgreSQL 12 it was implemented as an <strong>optimization fence</strong>. What does it mean?</p>\n<p>Easy to imagine an example with a generic data view aggregating lots of data. If the view is then used to select just few records it could mean a tremendous waste of computation, if the aggregation is executed immediately.\nInstead the aggregation should be executed only on a small subset of data, which can be deduced from the outer query.</p>\n<p>Unfortunately such counter intuitive behavior was true for CTE’s for a long time - their results were <strong>materialized</strong> only to be accessible for the rest of the query afterwards.</p>\n<p>Not to blame anybody - the creators had quite good reasons to implement such behavior (i.e. guarantee of exactly one evaluation, possibility of a recursive CTE’s and more) but this still feels like focusing on corner cases instead of optimizing the happy path.\nThat’s why the community <a href=\"https://www.postgresql.org/message-id/flat/201209191305.44674.db%40kavod.com\">insisted for a long time</a> for changing the status quo by giving the possibility of disabling the fence and unlocking the full potential of CTE’s.</p>\n<h3>Game changer</h3>\n<p>The SQL gods listen to their prayers and here it is - PostgreSQL 12 with updated <a href=\"https://www.postgresql.org/docs/12/queries-with.html\">Common Table Expressions</a>.\nBy default, when few constraints are met, the queries will be inlined allowing joint optimizations.</p>\n<p>It is still possible to force the old behavior - by defining the CTE <code class=\"language-text\">AS MATERIALIZED</code> the engine would execute it immediately.\nIt is also possible to hint the optimizer that we definitely want the CTE to be inlined, e.g. when the CTE is being referenced twice the engine won't inline it by default.</p>\n<p>This is truly a game changer for many developers who care about their queries’ readability, allowing them to substitute not very well liked subqueries with elegant <code class=\"language-text\">“WITH queries”</code>.</p>\n<p>Don’t get me wrong - not every subquery should be immediately replaced, they still have their strengths and in some cases they should be chosen over CTE’s. It is just convenient to have two distinct tools in a toolbox, isn’t it?</p>","fields":{"slug":"/postgres12-a-precious-release/","tags":["postgres","postgres12","release","sql","cte"]}}}]},"authorArticles":{"totalCount":2,"edges":[{"node":{"id":"4762bbde-b461-512b-93ae-22335b823a90","frontmatter":{"category":"Architecture","title":"Harmonizing Application Cockpit data using DB views","date":"2023-02-16","summary":"Our journey building the AUTO1 service inventory search using DB views","thumbnail":null,"authorName":"Mariusz Sondecki","authorDescription":"Mariusz is a Expert Software Engineer based in our Szczecin office","authorAvatar":{"relativePath":"pages/harmonizing-application-cockpit-data-using-db-views/avatar.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAWABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMEBQH/xAAXAQEBAQEAAAAAAAAAAAAAAAACAwAB/9oADAMBAAIQAxAAAAGv2Svu3GUctOAU5wh//8QAGxAAAQUBAQAAAAAAAAAAAAAAAQACERIiEyH/2gAIAQEAAQUCJkDB7KBW+bEJwkNZ6v/EABgRAQADAQAAAAAAAAAAAAAAAAEAAhEQ/9oACAEDAQE/AQMmMLPP/8QAGBEBAAMBAAAAAAAAAAAAAAAAAQAQESH/2gAIAQIBAT8BV2cmFf/EAB4QAAEEAQUAAAAAAAAAAAAAAAABAhEhQRAxUWFx/9oACAEBAAY/AqwSjpMj+oKvRU5PDZD/xAAdEAACAgIDAQAAAAAAAAAAAAABEQAhQWExcZFR/9oACAEBAAE/IWdbBmVNkJTg+IpKCEBHylkKGgktmegIYKT9dwgOydif/9oADAMBAAIAAwAAABAA18H/xAAXEQEBAQEAAAAAAAAAAAAAAAABEQAQ/9oACAEDAQE/EES8YEurv//EABgRAAIDAAAAAAAAAAAAAAAAAAEQESEx/9oACAECAQE/EDJJlS//xAAdEAEAAgICAwAAAAAAAAAAAAABABEhQVFhMZGx/9oACAEBAAE/ECQu0QVxCvgawa6iFVCFbUJDi/sW7HoRRveCLN+4F4gKruoOH4KtotWHILP/2Q==","width":50,"height":50,"src":"/static/41d7e93878c0ba2eb12594d317c2b361/d2d31/avatar.jpg","srcSet":"/static/41d7e93878c0ba2eb12594d317c2b361/d2d31/avatar.jpg 1x,\n/static/41d7e93878c0ba2eb12594d317c2b361/0b804/avatar.jpg 1.5x,\n/static/41d7e93878c0ba2eb12594d317c2b361/753c3/avatar.jpg 2x,\n/static/41d7e93878c0ba2eb12594d317c2b361/31ca8/avatar.jpg 3x"}}},"headerImage":null},"html":"<p>As you may have read about Application Cockpit in one of our <a href=\"https://auto1.tech/application-cockpit/architecture-intro/\">previous articles</a>, it is “The tool which lets concerned developers quickly find their bearings and gather relevant information pertaining to a particular service running in our ecosystem”.</p>\n<p>Over time, AUTO1 engineers and managers grew fond of using Application Cockpit, and started relying on it on a daily basis. The increase in popularity, naturally, led to development of new features, out of which, one of the arguably most impactful ones was the \"dynamic service search\" (or \"App Cockpit Search\", as we call it internally).</p>\n<h2>The challenge</h2>\n<p>AUTO1 platform currently consists of 600+ microservices, and on average two new services are created every week. Many times in the past, we needed to search our AUTO1 service inventory for services which were matching some specific criteria, e.g. are written in a specific language, using certain frameworks or libraries, or have certain features enabled.</p>\n<p>As it happens, this is actually where App Cockpit App Cockpit Search chimes in. From the user’s perspective, it is an UI that lets the user input some search criteria using the Cockpit Query Language (or CQL for short) and be presented with a list of services matching the criteria. One typical use case is searching the AUTO1 service inventory for the usage of a specific library in a specific version.</p>\n<p>Below is an example search for services using <code class=\"language-text\">org.springframework.boot:spring-boot-starter</code> dependency in version <code class=\"language-text\">2.3.0</code> onwards:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/1b3a431578edcdb6a06f758c6017fa35/e4a0f/search.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 33.56678339169585%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABS0lEQVQoz02Py0oDQRBF56eETHdXv+aZYUSEhAQScKPLIIqIG9FlEBHcuHHh17gRfCAmioT8yrVrxugsDre66HurKnrY/cB86xE36RPu999wO3zFvHrBpX3G1fAdd7NPXO8tcbGzwGn2gWO/wMwsMfNfOKq/cTJY4WyyxvnBCoeDNaLM9lGkfRjy0OQCQVVQ6eGsh9EeKjaQARJtn7oo9qUo6zGq7REiYzUkSQgSEEqALEEHnLMN1hkUZY5+VYK0ghA9SBn/IRoEkrJGXtWIhFSI4zggGiUVArUOxjBASBBpjEZjTCdTFEUJay289/AuXOBcWweUlDDGIJJK/ZpbuMlwLcMnhgcw3QBm806SJHj4Go9I0f823UAOUmEYk+d52K5AmqbIsqx5d2sXtiYe6JP25F6v15zLSkRNGNesPITNbOSATQhrt7/Z8AenEAYoBp8eJwAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"search\"\n        title=\"\"\n        src=\"/static/1b3a431578edcdb6a06f758c6017fa35/40fad/search.png\"\n        srcset=\"/static/1b3a431578edcdb6a06f758c6017fa35/707e9/search.png 148w,\n/static/1b3a431578edcdb6a06f758c6017fa35/649e0/search.png 295w,\n/static/1b3a431578edcdb6a06f758c6017fa35/40fad/search.png 590w,\n/static/1b3a431578edcdb6a06f758c6017fa35/b3fef/search.png 885w,\n/static/1b3a431578edcdb6a06f758c6017fa35/301c0/search.png 1180w,\n/static/1b3a431578edcdb6a06f758c6017fa35/b5a53/search.png 1770w,\n/static/1b3a431578edcdb6a06f758c6017fa35/e4a0f/search.png 1999w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<h2>The dilemma</h2>\n<p>Once we were certain of what we wanted to achieve, i.e.: search our AUTO1 service inventory using dynamically defined criteria, we began the efforts of defining the architecture.</p>\n<p>As you may recall from our previous article about the Application Cockpit, we use <a href=\"https://backstage.io/\">Backstage</a> as our Front-end. Backstage is fetching data from our backend-for-frontend service called “Application Cockpit Data Provider”, which in turn, is backed by a PostgreSQL DB.</p>\n<p>As Backstage itself comes with <a href=\"https://backstage.io/docs/features/search/search-overview\">search functionality that</a> could be potentially used for our purposes, we were left with two options, either to reuse the already provided solution and tailor it to our needs or create a solution from scratch.</p>\n<h3>Why not reuse what is already available</h3>\n<p>After evaluating the Backstage search, which at that time, was still in the early alpha stage, it was clear to us that it would be a bumpy ride. As it is a full-blown search mechanism, or to put it more bluntly, an over-blown mechanism, compared to what we really needed:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/60a3e1f05a8e86ae0f23e39adaabb9b8/2d28b/architecture.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 96.85230024213075%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsSAAALEgHS3X78AAADtklEQVQ4y5VUW2ibZRj+ZOgYipuuwqYXgowhQ/BS0alM8WaK6LXe6spABBFRxFypw8kmeK8ytQdX55pmy5bp1kOWpkmTZUlPyfI3WdKmp6SNbXNo8h8en+/786ejF4Iv/8t3+L/3+d7nPXzi+3Qcn0+F8GUygi+mw3BxdM2M4zPqlaU8pJiWBYtqcm5wNCyTc2dtqv9QnwXREeyHGPyN2gUx1LU9/+tndKYjClCnkTSgJdqyY221LhWHx70QN/vw6Fg/9ocHsGfsInZThb8Xn2Rutw20tSLcyQR8OQ2BlQUM39Vw6c4UvHOzmCwuSUR1ThwMe2jcQ9DfIQLnbR3to5e/4mQ62gb8NjfFM+dxf+APHAgN4Ej0Kh6jA2KoG53JMCT/uq5DHJ8YxpFbPrwQv46X4jfwIvUY9fmoDz/Mp8hXB5o6vspOQgz3YH/IjaeiV3CI2kFW95Hdh9otRb9cr0Ho0oAyUVqGLz+LG3MZDC7kENwoIbu5jkwmg8pyEa6cBGR8A714iKAHxtycSybn8L5mx3q1VoWoNhsw6MFriSG6T4ORXuwKXqBxF05qpLzVwGa1inRlHZ6VOVymDpUW4adeLc5jYCWPJP/JJP0jPazpTRXQt2ZGIYJ9eIAJ2UsqMqYfZ+Pq5qZhILS2jFN3YjirJfAdS+1UKoYzHL/hXqC8os6VtwFBwIBKhgTcE6CHfgLOxuzyMEy47k6Q3i928pjExyOXsDv4p6L8gSwvq0XZAXx92s+D3Soue8cHVE2ekNmT5cCAfy2zPNKNXaO8jJl+eWIQhyJetfeRvJiy0diCqBNQFmS8XMTf7Az/6iJ8pQIuMz7J6nq7bE7npxmSC3h43IMHw248wsTsC9mJ+bQVmgrzoQClxJYK8GgzuFnIwWQi7m0IwzRRrFaQZnFrayWkVotIl1dRqFeRr1dQbm6pszUH0GLQ35gcUTHs4K1abaPdcibsDjhX0PBcxIej0Wt4NuzFsdh1HKfN0yzws7JeKbJihN3YFt6e9KtOOBjyIFvbbD8KRqulXNmE6qLDt6/Z9cf5k1GvSk5nMqTOrG/VIRxqxUYd2eoG5klDN812w+sOYCahEvUEAcRIj+r1faF+lekTqbBdXqYB4bwSO8XZczy8WJrHq4lBvDnlx7upMbyXCuGd5CheYZv+uDjbZiQcQ7lw1NnbOaLl+U+LGZyZS27v2c+h/XzhP8TxXinXdUNXRf4MHxPZTfIdqDabbRb/G7BJAIsA7qUcuhcyMBoNe++es/8CTroLQ3iIY5gAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"architecture\"\n        title=\"\"\n        src=\"/static/60a3e1f05a8e86ae0f23e39adaabb9b8/40fad/architecture.png\"\n        srcset=\"/static/60a3e1f05a8e86ae0f23e39adaabb9b8/707e9/architecture.png 148w,\n/static/60a3e1f05a8e86ae0f23e39adaabb9b8/649e0/architecture.png 295w,\n/static/60a3e1f05a8e86ae0f23e39adaabb9b8/40fad/architecture.png 590w,\n/static/60a3e1f05a8e86ae0f23e39adaabb9b8/2d28b/architecture.png 826w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Source: <a href=\"https://backstage.io/docs/features/search/architecture\">backstage search architecture</a></p>\n<ul>\n<li>\n<p>The Backstage search was “bloated” with components that we don’t really need, due to the nature of the data and queries we planned to execute (like collators, indexers, and schedulers). As we already had our integration layer performing calculations on our data and feeding it to our UI, we didn’t need any of these.</p>\n</li>\n<li>\n<p>At that stage, the feature-ready predefined UI components connected with the search plugin did not provide us with the required flexibility, which we managed to achieve later with the Cockpit Query Language.</p>\n</li>\n<li>\n<p>The usage of the Backstage search would require us to fork some of the Backstage search plugins, adopt them and then maintain them ourselves. On top of that, it would require us to make adaptations also on the data structures themselves, which was something that we were not very eager to do.</p>\n</li>\n</ul>\n<p>Long story short, although the solution seemed to be promising and the architecture was quite neat, the shortcomings outweighed the benefits for our use cases. Hence, we pursued the idea of creating the solution from scratch.</p>\n<h3>Making our own solution</h3>\n<p>As you may recall from the <a href=\"https://auto1.tech/application-cockpit-model-outline/\">Application Cockpit Model Outline</a> article, our data model consists of only two types: Resources and Readings. The latter type comes in different shapes and sizes, as we store the actual Reading values in form of JSON objects.</p>\n<p>Since we have different JSON schemas for different types of Readings, we need to harmonize them, in order to make them “generally” searchable and combinable to support logical query operators such as OR and AND without changing the actual stored data structures.</p>\n<p>As a simple example, where we would like to search all services built on a specific version of a docker base image, which is implemented on top of a Spring 5 Framework, where the main programming language is Java. In terms of data structures, this is how it would be represented:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/57d0fd9d41df05d488d2bb258362507d/81d54/s3.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 32.96875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABq0lEQVQoz31RzUsCURB/pdYaCN18G8IqXhViwcSsQyB461/w6FHwj4g6CApepATrIhjJLkopIn5/X9pV/FhXQg8pSdDRi9g8UehQDfx47838ZuY3bxD6Yfl8foecpVKJrdfrj9ls9j6Xyz2k02mCSKVSeWq1WheEA/Fd9JtNJhMVkJTNZlOZyWT2iC8YDJrhnQa8VavVgSiKEnAkuMvRaNRKOMlkUgF+1G631+h0OqhWq6E/zeVyMaFQiIXiplgsZo5EImaPx2OC0B76z8bj8W2j0biCEW6KxeK1LMu+4XDo0+v16g1FBVASeL1eVb/fP1gul/uLxYJarVZUIpGg/H4/FQgEKLfbrUKj0SgHo3wVCoWPcrn8CdLfB4OBnygRBOFyOp3K8/n8dTabidBYhLEEaC4SQJ4oSZIIHBLvQ60kstvt6lQqdR6Px0+AdMpx3PFWfTgcPoMCL91ul4MYD4vhoSkPPh4Wx8O/ru+9Xo8DQc+wwDuSpwEotmMRZVar9XBTU6PVao9YltXSNI11Oh0GAdhms2GLxYKdTidmGAY7HA5sNBqxwWCgvwHc0/KbUbht7QAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"s3\"\n        title=\"\"\n        src=\"/static/57d0fd9d41df05d488d2bb258362507d/40fad/s3.png\"\n        srcset=\"/static/57d0fd9d41df05d488d2bb258362507d/707e9/s3.png 148w,\n/static/57d0fd9d41df05d488d2bb258362507d/649e0/s3.png 295w,\n/static/57d0fd9d41df05d488d2bb258362507d/40fad/s3.png 590w,\n/static/57d0fd9d41df05d488d2bb258362507d/b3fef/s3.png 885w,\n/static/57d0fd9d41df05d488d2bb258362507d/301c0/s3.png 1180w,\n/static/57d0fd9d41df05d488d2bb258362507d/81d54/s3.png 1280w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>As we already use Spring Data JPA as the persistence layer to communicate with PostgreSQL in our data provider service, we have limited our options to two choices: dynamically built queries on top of joined database tables or database views. Either of them seemed to us, initially, as a good candidate for dealing with the complexity that stems from the heterogeneous data structures.</p>\n<h4>Dynamically built queries</h4>\n<p>The idea behind this solution was to create a set of SQL queries, backed by corresponding Spring Data JPA repositories, one for each attribute in question, execute these queries against the DB, and then combine the results on the Java side performing the necessary filtering and aggregation.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/c29280ac17f26f05c90b05877ded20fb/81d54/s5.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 30.703125000000004%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsSAAALEgHS3X78AAABnElEQVQY0x2QTY7TQBCFcwbW/EiAxAGQECuERsyCFRdiAVyCCzBo2LFjlw2LiKAZeyInTvyTONhxnMSO/7ptt93dj4LFU9VT9StVf6O7JL8/z9iTBHgck/wWTzvgnlOK62Xe2quqv/G5MjYdjHULw+Xq1uXapGrQ7OGyEC/tvLVIc4fJq9Gy7H+RgeUHPMlLlvbAeLH5NPXDqcckrDOHnSqcuQYTApEE/oCk/9dHfofLPfl/criejdwGZkTGLTrtl53KqLcy/mGRteMdhWe70zDb5Wq1O6lVlKj5sZLbVmuvHvR04by2Tuxt0AIxvd00ajqi82+3A5BkuexE31MLj/fvF3s9LhqJtMilnfQ6iBIdnSttR1wzzrW1jfH1+tuXq+8/PnqHXlEMWSN+00LchWSjfQLGmG5oMPHCz/PoYPqVgBlnsOIc60OOpBngpyV8QuHUAyZL/7kZn99tCVNECFwmb0ZOLV94Dd7sFS42XF6suboMBJ4R7IldCOmyoTKirg6PRX1M0zosWL2iIynDAo0Ha4FX9EtB6onhz7+6pakgZsnNFgAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"s5\"\n        title=\"\"\n        src=\"/static/c29280ac17f26f05c90b05877ded20fb/40fad/s5.png\"\n        srcset=\"/static/c29280ac17f26f05c90b05877ded20fb/707e9/s5.png 148w,\n/static/c29280ac17f26f05c90b05877ded20fb/649e0/s5.png 295w,\n/static/c29280ac17f26f05c90b05877ded20fb/40fad/s5.png 590w,\n/static/c29280ac17f26f05c90b05877ded20fb/b3fef/s5.png 885w,\n/static/c29280ac17f26f05c90b05877ded20fb/301c0/s5.png 1180w,\n/static/c29280ac17f26f05c90b05877ded20fb/81d54/s5.png 1280w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>The advantage of this solution was that we would keep things small and isolated. Each query is in a separate repository. Each one would deal just with the data structures it's aware of. On the other hand, this solution would come with a significant performance penalty, due to the substantial amount of data that would be transferred from the DB, just to be later filtered out on the application side.</p>\n<p>The alternative solution would be to dynamically build a large SQL query that would perform both the retrieval of the data and the filtering and aggregation of its results.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/3f73e83b5b0a65e69919bc1905dcfc6b/81d54/s6.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 42.34375%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAAB40lEQVQoz31Ry27TQBSNUomHBH/Cmh/gG9jCglUlFiDBiv4AG8SGsqASEgskBJt0UxWJpEJFQaKiok1s13acxk7s+Bm/xuPHjC93ki4RVzqae+17zpl7pyOv6Ecpoo5RgaaXYKgFTLQCppMStAXAXTVj0wgATA4wYwAh5hppZ0Mnvd7BUHO+l+A3D4G9Rx0kH7tYjA0T7FUKScVgWeHPpIIfv//snHvJSI+RQJo6plVtly2gqYSUW0IQjd/YWEwb5JD2QAj2LXRWLbtxk5yHOWWXScFPFhH/sH/44MRyxzLaW37CHc/jihvBz6nj9Xq9HdS7LYVkV9zaxwmMnB0KwW9zVHfDqCZVzRrGm3lKuZLU9Swr7xsZk+0SIKA1D2jDYzQf6tbFp89fnj15vH1v//uv9zoqMtaCldK14HAldmTOgBYFiEAiyEEGp5L8ajz3zbM5rsEL+NLz2CrNYOTG0stj9ZoY+SKtd5ct8kuxw+agg3O/Nop2GDfQD0s28Ek5MElzpFfQvwS4M6Ew8tEEzzUWm1zBS9wQgnoB73DF4G4ea9D5X5yZzk1sklUCHE9yhRZxqsTVWlDL+QusdYSEL/72n0LwdXt9Kmm9hY2PEM8RT68g8oeIrugZhUX3PCBbG+Tdv+VV7Ki32nQdAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"s6\"\n        title=\"\"\n        src=\"/static/3f73e83b5b0a65e69919bc1905dcfc6b/40fad/s6.png\"\n        srcset=\"/static/3f73e83b5b0a65e69919bc1905dcfc6b/707e9/s6.png 148w,\n/static/3f73e83b5b0a65e69919bc1905dcfc6b/649e0/s6.png 295w,\n/static/3f73e83b5b0a65e69919bc1905dcfc6b/40fad/s6.png 590w,\n/static/3f73e83b5b0a65e69919bc1905dcfc6b/b3fef/s6.png 885w,\n/static/3f73e83b5b0a65e69919bc1905dcfc6b/301c0/s6.png 1180w,\n/static/3f73e83b5b0a65e69919bc1905dcfc6b/81d54/s6.png 1280w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>However, this approach would come at an increased maintenance cost. As every data structure change would require a corresponding Java code change, hence we would lose the benefits of having the data values stored in the JSON objects. On top of that, maintaining a large SQL query would quickly become a problem on its own.</p>\n<h4>Database views</h4>\n<p>Knowing the shortcomings of the dynamically built queries, we decided to evaluate another promising approach - the database views, that could hide the complexity of dealing with the heterogeneous data structures:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f74d7564313196d1e352021f2f81dac4/81d54/s7.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 48.90625%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAACH0lEQVQoz22S224TMRCGF8ET8iDcIPEaiBvEDSoPgYSEaGgEqoqqNkmhVZLNYTcH0uw5m/X4sLZ/JmmvUC198ti7M5759QfBE4uEeH7YpaRXRZ7116vlqRSi+0DT5e9d8Ygk0VUkzpgffP7yVL0AdXosuG/EB/AqhIR0gPIMn9vDJTz4CrkBEuVQWGAnyAZ3Bb1lzica30YSnaHAaUg4nxh8rLQ72XDSsNb6ttL2bmfsWMIm2lujpCVtbKy8DYWzo8YjF6oKQuF/ZZw03eao+BXiuGQGm7IZzaKrmFualN6lZHwiNEaVwu/5Eos4wnIZox8Rqn3r961FJlQdTAidBbd9N4vNKitsWjftfCdxPr+fD0bh12ndYhBLu81Lv0nSw0O4Cue46fcwCkP04xThtvQR5xSk62As0FlzZ1m1M43UVmrTrlmwYd1Ok3r/OWHdFrW1CWmfkEG0I9xEK2w3f0FKY8aFbjeFHxaEUpk6YL0uCh5xuVjAGnOUO9UevWVSzxbx5ZRVv4mUK4vMp1mGVVZi3WgYrUCS8Gfd4j4jn3Px48jjxr2bCXdRaNfJVHuWNPJ7JOzF2OCk1PbTUV+ymrV2E2Yk4DbUOraP2xO5Wd0yxk6EQ05q96Rtgpevnx22oqH34lBQ8tj2gYg5WMTbFsY6bDk+SLbl/yohVXALBPgfo148+vBNKeScfXjJ9B5QPfZbj83dE49wfM0MOP75D9ocmX886x8iAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"s7\"\n        title=\"\"\n        src=\"/static/f74d7564313196d1e352021f2f81dac4/40fad/s7.png\"\n        srcset=\"/static/f74d7564313196d1e352021f2f81dac4/707e9/s7.png 148w,\n/static/f74d7564313196d1e352021f2f81dac4/649e0/s7.png 295w,\n/static/f74d7564313196d1e352021f2f81dac4/40fad/s7.png 590w,\n/static/f74d7564313196d1e352021f2f81dac4/b3fef/s7.png 885w,\n/static/f74d7564313196d1e352021f2f81dac4/301c0/s7.png 1180w,\n/static/f74d7564313196d1e352021f2f81dac4/81d54/s7.png 1280w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>In that approach, we would create a database view that could look like the following (based on our previous example):</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a7ee301a6aafa6fabcdae5a177368b93/283a5/s8.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 47.29299363057324%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABTklEQVQoz32S6W6DMBCEeZti4/vEmCNJI1Xq+7/PdHGOplHSH5/WC+zYM6ZbgkAwAkbbxsAZvFEIMUEIcUVeufSccwzD8JLOSA5nJEpZUMKM7BK8NTDWkThvw8/sg+9EO8Y4hJTIqZBgRYkzRj/dxV4NPT9/7Lv+QbDGtYlOJJpchnhj679NOkeWvbMo04o6bkixUl0RXKAPOPq+B2OPcIL9hXK/xdEFzRG8xzRmLCUREfN2xFQXxJTb5fgQiQDnqVoNt2N8u0RnPax2vydktPjoGZLhqJbhlCXqsmFeD1RXLCS+Hj7v62XdMJVM8XiKaUJJM2IcEX2GkhrdnhPjA0YrMPsB51FjPZ6xnc5NdK/nr28SOzVmclETOcoLxlQx5on6mcTUxfItUCkGKMLIAda59tsorWEM2aJI7N4rDU3vlVLtNPtlam1oTt4t/wBYyxnZhQ7mogAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"s8\"\n        title=\"\"\n        src=\"/static/a7ee301a6aafa6fabcdae5a177368b93/40fad/s8.png\"\n        srcset=\"/static/a7ee301a6aafa6fabcdae5a177368b93/707e9/s8.png 148w,\n/static/a7ee301a6aafa6fabcdae5a177368b93/649e0/s8.png 295w,\n/static/a7ee301a6aafa6fabcdae5a177368b93/40fad/s8.png 590w,\n/static/a7ee301a6aafa6fabcdae5a177368b93/283a5/s8.png 628w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>which returns:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/5509b1df352d53ae1e87ab45b44def49/81d54/s9.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 14.21875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAADCAYAAACTWi8uAAAACXBIWXMAAAsSAAALEgHS3X78AAAAaUlEQVQI101PBwoAMQzq/z/bvYeHQuAKEm2sSd1aCyklzDlBnnNW3XsjhIBaK845MF9rTT3Cey8P7+jrvcPRzCbPvVcwPsYQf+9Js5omGMJADooxSiuQ29kjauOcaEH/nulSigL5K9v+A14p6uLJktOhAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"s9\"\n        title=\"\"\n        src=\"/static/5509b1df352d53ae1e87ab45b44def49/40fad/s9.png\"\n        srcset=\"/static/5509b1df352d53ae1e87ab45b44def49/707e9/s9.png 148w,\n/static/5509b1df352d53ae1e87ab45b44def49/649e0/s9.png 295w,\n/static/5509b1df352d53ae1e87ab45b44def49/40fad/s9.png 590w,\n/static/5509b1df352d53ae1e87ab45b44def49/b3fef/s9.png 885w,\n/static/5509b1df352d53ae1e87ab45b44def49/301c0/s9.png 1180w,\n/static/5509b1df352d53ae1e87ab45b44def49/81d54/s9.png 1280w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>The main database view, here called <code class=\"language-text\">document_view</code>, would be the aggregation of multiple sub-views. Each sub-view would only know about its data structures, hence we would achieve the desired single responsibility and encapsulation traits, making the solution more extendible and more maintainable. Even if something would be changed within one of the sub-views, it would not affect the aggregated view or other sub-views.</p>\n<p>As previously mentioned, since we use Spring Data JPA extensively, the database view would be reflected in the code as a JPA entity which would allow us also to use the Criteria API to build a generic query on top of that main view and utilize the built-in PostgreSQL functions, that would come especially handy when interacting with the JSON objects.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/2a3224488b066b930c4198c8fd7efce4/77f8d/10.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 566px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 106.00706713780919%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsSAAALEgHS3X78AAACpklEQVQ4y5VU2XLbMBDT1yQWT4kSqcPWacd1fKT9/59BQTnxJG3SuA+YHXGoXewC3OTlUuJydhBCLpDy/yCE+BCTaTKYJwHnVpAihUgFHh/TG1J+p2m6IBZ8O189prckV7wm7Du9JBynCrkriAzj7DGMBSZG5wxc4VGUAdpI9EPBuyWjg7Eaq9XqWpDFYky6mHBOmaBcLs+TxOnS4XDcY5hqVK2DDyXW3YBuGPF0mDDvOjyf98t3qFtUVY0mS9FUAck8GOzIcNikGDuJYbAIIYfSGaunkOrajlIKxli2qcgknmkyNsuZiVEJRo2kLgyqTKK0ElWulop1u2br7jbsq2Axvs1NvH6/x/VOIqThz8yeCTysBIf9yKE/kMXqPxV/FUVpg5ALdIVEcGy3aRF8g8L6RfGosvzDGv9CopnQGtomj7CL0nEmTePQrnO40kC8Y/BtwjjcJkhsOyrdBQzznqoGPD11OJ+2VL2HYdF72N0YalbPqKahklE5azPkhuysQ5HRb1r99SK+ZqiocqCxybAjw82wpZEdjqcOl58DXn6NtFK7zPMuhkpr5FqizSV8FIW2KYtAdh4+r5foTAn1ybv9PCEZOtrGhxSlz4mKbSuM2xLbfeCrKDHtAmxm71NZKk3bSEyVwLoqsO4njqDGuOmxHSbsxi02bb3Y5y6G722TR9vkBTRF8MGiqjM+Q46hisiW5/e9KLRN7SXmdYp+E9CNO3jvcfgx4Hye8XwYcTpyIcwN4ry/TRhfiqFt3GIbIsuQ0Spa5DwvlhghU3lvy7EdWmaQtMx4FYWLoum5ttiq45N0hSI7dU0m7/BhQx/u6MOePuynPbdNi+ORHrzMbHvC6TSStf3A8CskNnew3GWBrBw3cOEDmdWw2iG3JTLDLU6oO7fOb1OAjupds4TxAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"10\"\n        title=\"\"\n        src=\"/static/2a3224488b066b930c4198c8fd7efce4/77f8d/10.png\"\n        srcset=\"/static/2a3224488b066b930c4198c8fd7efce4/d406f/10.png 148w,\n/static/2a3224488b066b930c4198c8fd7efce4/5e8ba/10.png 295w,\n/static/2a3224488b066b930c4198c8fd7efce4/77f8d/10.png 566w\"\n        sizes=\"(max-width: 566px) 100vw, 566px\"\n      />\n  </span>\n  </a></p>\n<h4>To materialize or not</h4>\n<p>As the DB views seemed like a good solution, we still needed to answer one more question: whether to materialize them or not. As materialized views would be, obviously, faster to query, but that would not come for free, as we would need to build a mechanism to frequently refresh the materialized views, which would also induce a performance overhead. In the end, as we expect the App Cockpit Search to happen fairly infrequently and we value accuracy over query execution time, we’ve decided to not materialize the views.</p>\n<h2>The conclusion</h2>\n<p>Finally, we decided to move forward with the DB view based solution. Although it’s not a silver bullet and requires us to keep an eye on the query performance, as over time the number of sub-views will grow, which might impact the overall search performance, we consider this to be a fair trade-off for the gained extensibility and maintainability of the search functionality.</p>","fields":{"slug":"/harmonizing-application-cockpit-data-using-db-views/","tags":["Cockpit","PostgreSQL"]}}},{"node":{"id":"89a8474c-7217-53ee-bffa-b7a1d7f79b26","frontmatter":{"category":"Engineering","title":"The OpenAPI journey","date":"2021-11-22","summary":"A story on how OpenAPI helped AUTO1 Tech to move forward faster and in a more structured way.","thumbnail":null,"authorName":"Mariusz Sondecki","authorDescription":"Mariusz is an Expert Software Engineer based in our Szczecin office","authorAvatar":{"relativePath":"pages/openapi-journey/avatar.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQDAQX/xAAXAQADAQAAAAAAAAAAAAAAAAABAgME/9oADAMBAAIQAxAAAAGbumAap5ikb5hWqFs3/8QAHRAAAgEEAwAAAAAAAAAAAAAAAAECAxETIhIyM//aAAgBAQABBQJ7KyiZB+eTXkVOkRpX/8QAGREAAwADAAAAAAAAAAAAAAAAAAERAhAx/9oACAEDAQE/AVIRmPHr/8QAGREAAwADAAAAAAAAAAAAAAAAAAERECFB/9oACAECAQE/AXaaHzH/xAAdEAACAgIDAQAAAAAAAAAAAAAAAQIRMUEhUWGR/9oACAEBAAY/Ar1dFxbNkpdSOHZgf0XphH//xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhUTFBcZH/2gAIAQEAAT8hdJaGSDyw7NxOoAXu4myepjz9sxM4YXnC4zJPSf/aAAwDAQACAAMAAAAQsAe//8QAGBEBAQADAAAAAAAAAAAAAAAAAQAQESH/2gAIAQMBAT8QHFxNnbf/xAAYEQEAAwEAAAAAAAAAAAAAAAABABARIf/aAAgBAgEBPxBrVD2MJ//EABwQAQEAAwADAQAAAAAAAAAAAAERACExQVGB8f/aAAgBAQABPxAeZq9WVmcC80YLiXbfcFAVSC9AmMF6FjWIaILvbd795a6UQeU/MlrKKMsHZ0Fz/9k=","width":50,"height":50,"src":"/static/84204f0ce4111c333b6172133a845a85/d2d31/avatar.jpg","srcSet":"/static/84204f0ce4111c333b6172133a845a85/d2d31/avatar.jpg 1x,\n/static/84204f0ce4111c333b6172133a845a85/0b804/avatar.jpg 1.5x,\n/static/84204f0ce4111c333b6172133a845a85/753c3/avatar.jpg 2x,\n/static/84204f0ce4111c333b6172133a845a85/31ca8/avatar.jpg 3x"}}},"headerImage":null},"html":"<h2>The OpenAPI journey</h2>\n<h4>The situation</h4>\n<p>The AUTO1 microservice landscape is growing in size day by day, with new services from a spectrum of languages being erected and usually communicating with each other by using REST contracts.</p>\n<p>In a fast paced environment, such as AUTO1, often the service providing the REST API and the service consuming this REST API are developed in parallel, by independent teams. On top of that the business requirements over the lifetime of a project evolve, thus impacting the stability of the API. Having these moving parts in picture can make the development process quite challenging, especially when aiming for high productivity, zero downtime and independent development of the client and server parts. Hence, having a clearly documented and language agnostic API is vital for the success of the whole process.</p>\n<h4>The light at the end of the tunnel</h4>\n<p>After extensive research, we found a great candidate seemingly accommodating all the aforementioned requirements - OpenAPI. In short the OpenAPI specification defines a language agnostic standard allowing to describe REST APIs in a human readable form, imposing a unified structure, clearly and comprehensively aligning expectations between the consumer and provider sides.</p>\n<p>Below is an example of such an API described using the OpenAPI 3.0 standard:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a7f250f18c1768e91dfab8b2437f95a5/5d76c/img1.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 91.375%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsSAAALEgHS3X78AAABR0lEQVQ4y41TCXKDMAzMc1Ks2xeQNjT//1RlKCFHp7AjGxm86D5NGS+Jayk555gyIoWuC/8CABbl1Bt+JiyJxj6mZCyM67d9sl+9FahJJMaYk28icpTcBahuvEYk6maEPWxk14uGa3XLeY22e8ffZGKaSpOoJKrsTov6Yv5VlycR4Yq7fvJVFNyymrAQO93FuCnmZ3I7gAgPwPV48uXVGgxTVFNZfhkWv2Av5shYFSKFJGiMql6tw9keI3ids1JW9B+FLnTHmwQhfFW59mnsk5e5hfMgO2QhnDJMlS5VchRWIkGStiPhDtm1ocS+FrPoGXe3N9l1m5rbMZdyPn8caa9Xy71Bnz3NFo7hqbcHg9tg1sgdLPV/aop2+fW8DYbCd2WP3NOtrauw5Yw3QYL55ZzFNrIPg5E4jNnKcPGmnefiTcLTfif/APHyvwWxcOnkAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img1\"\n        title=\"\"\n        src=\"/static/a7f250f18c1768e91dfab8b2437f95a5/40fad/img1.png\"\n        srcset=\"/static/a7f250f18c1768e91dfab8b2437f95a5/707e9/img1.png 148w,\n/static/a7f250f18c1768e91dfab8b2437f95a5/649e0/img1.png 295w,\n/static/a7f250f18c1768e91dfab8b2437f95a5/40fad/img1.png 590w,\n/static/a7f250f18c1768e91dfab8b2437f95a5/b3fef/img1.png 885w,\n/static/a7f250f18c1768e91dfab8b2437f95a5/301c0/img1.png 1180w,\n/static/a7f250f18c1768e91dfab8b2437f95a5/5d76c/img1.png 1600w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 1. OpenAPI specification example</em></p>\n<h4>Design first, code later</h4>\n<p>In general, we distinguish two approaches when developing APIs. The code first approach, which is basically about developing the source code fulfilling the business requirements and afterwards, optionally, generating the API specification from it, which for OpenAPI can be done using the <a href=\"https://github.com/swagger-api/swagger-core/wiki/annotations\">swagger annotations</a>. The second approach known as design first, is all about describing the API in a human readable language first, by using for example the OpenAPI specification, and then optionally, based on this artifact generating the source code for server and/or client. Naturally, both approaches come with pros and cons.</p>\n<p>As previously mentioned, in AUTO1 it often happens that changes that are being introduced in the API on the server side need to be implemented at more or less the same time on the client side. In such a situation, if the code first approach would be practiced, the consumers would need to wait for the server side implementation to be delivered before starting their part, that would be simply inefficient. Here, for the rescue comes the design first approach, where from the moment when the API specification is agreed upon, both server and consumer side can kick-off their side of the implementation, independently. Additionally, this allows to spot misalignments already at a very early stage, and rectify them before the actual implementation from either of the sides is started, hence less expensive to fix.</p>\n<h4>The ecosystem</h4>\n<p>The OpenAPI comes with a rich ecosystem, from UI tools such as <a href=\"https://swagger.io/tools/swagger-ui/\">Swagger UI</a> allowing to visualize and test ad-hoc the specifications to <a href=\"https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html\">AWS integration</a> providing a fast lane to expose the specification via AWS API Gateway.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/3434af62ba0a90ef3305143d041e0a9f/5d76c/img2.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABWUlEQVQoz43QS0/CQBAA4P5SUVE0Gh/xYOI/860RQjx5VIgRi9SWUvb9mNktilujNytMvsMcZnZnJkJJxo/tJB4M4wEp8hL0p7cztN5qZ2Qdq5gDEznNxPABRBESVAQV9YZ5w0v7txmIUEbzERoZgZEse5XT1Iqp07Sipk7mdbwuAicyZ0XkQPEiCd+WqEtQFStKQ+tIkslpZtgYTdWss1HMxu8oKchq7PCQM7zOOBnmyRsvMqtFpDg5OT7a2d3cO9jaP9xuba41GyvN1Vqt5urGeqPVbAyen8LO4r591W1fdDuXQef2vH1z9o+769O767POzSkjeRjboObzDz+fl79mi336mYMo7C2L4WT0rEmCIgW+GIqMpi9W0upnUCRcwttAOMuXgYaGS0eASAhV2gRaGwtgARcKdYgYOe8nk0nyHVmaMsb4cvHTHMdxr9fr9/shCc10uQCAL5G0uXhSdaMzAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img2\"\n        title=\"\"\n        src=\"/static/3434af62ba0a90ef3305143d041e0a9f/40fad/img2.png\"\n        srcset=\"/static/3434af62ba0a90ef3305143d041e0a9f/707e9/img2.png 148w,\n/static/3434af62ba0a90ef3305143d041e0a9f/649e0/img2.png 295w,\n/static/3434af62ba0a90ef3305143d041e0a9f/40fad/img2.png 590w,\n/static/3434af62ba0a90ef3305143d041e0a9f/b3fef/img2.png 885w,\n/static/3434af62ba0a90ef3305143d041e0a9f/301c0/img2.png 1180w,\n/static/3434af62ba0a90ef3305143d041e0a9f/5d76c/img2.png 1600w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 2. Swagger rendered view of an OpenAPI specification</em></p>\n<p>Another very helpful set of tools are the <a href=\"https://openapi-generator.tech/docs/generators\">code generators</a>, that can generate client side libraries and server side stubs from the OpenAPI definitions. These extendible generators support a variety of languages and frameworks, which lets one move faster, especially in a polyglot microservice environment. For our Spring Java applications we have decided to go for a <a href=\"https://openapi-generator.tech/docs/generators/spring/\">generator</a> that is very well configurable and easily customizable. It can be executed in various ways such as via cli, maven/gradle plugin, etc. Since our Java projects use maven for the build automation process, the maven openapi generator plugin was the natural choice for us.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/aae6f743e2adb089391e73f024c93246/418da/img3.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 43.916913946587535%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAAsSAAALEgHS3X78AAAA+0lEQVQoz4VRi2rDMAzMb622FMcvWc7DTtKuLaP//xtTuq0wEqg5hLC5O+ncMNn7OQ05REqJcyTmFMY+eG8BTft3NOAeTdeZc43XlWud1qXMdcyZc06iJXoUbYo2hu6YLKpziZclUfTSm05M4KRAadBaKipAqcdkeRDhy5LF0PkQAsXoRAUAAY45/8gy+W0lIfuQcvLetR8KTnrzf0921khma6GpD9bZ5zFy6ZyRFZTeA3/QyEoa8TLT48qP21hrmaZpmce59HPtmVli+w0vsUB6Ij9kx+QacS9D+PrkpQRrt1S3gTVuUAjY7oAvNJKWfFUiKyG9XfIF9azf37eKwn0eyZ8AAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img3\"\n        title=\"\"\n        src=\"/static/aae6f743e2adb089391e73f024c93246/40fad/img3.png\"\n        srcset=\"/static/aae6f743e2adb089391e73f024c93246/707e9/img3.png 148w,\n/static/aae6f743e2adb089391e73f024c93246/649e0/img3.png 295w,\n/static/aae6f743e2adb089391e73f024c93246/40fad/img3.png 590w,\n/static/aae6f743e2adb089391e73f024c93246/b3fef/img3.png 885w,\n/static/aae6f743e2adb089391e73f024c93246/301c0/img3.png 1180w,\n/static/aae6f743e2adb089391e73f024c93246/418da/img3.png 1348w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 3. OpenAPI generator plugin configuration example</em></p>\n<p>The code generators have become pretty fast a very prominent tool among our developers, for various reasons:</p>\n<ul>\n<li>\n<p>They immensely reduce writing boilerplate code required for setting up the communication channel with the REST APIs, hence letting the developers to focus on the business code rather than the glue code.</p>\n</li>\n<li>\n<p>They generate repeatable and consistent code of the API model and related REST client definition.</p>\n</li>\n<li>\n<p>Being highly configurable, they can be used out of the box and fine tuned without writing a single line of code in many cases. A good example here could be the Spring generator that allows us to define things such as the time API to use for the generated code, whether to wrap optional parameters in a Java Optional container or whether to use any framework specific features such as Bean Validation 2.0 from JSR 380.</p>\n</li>\n</ul>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/d836f12eedc71eca7c971d867e21f632/4d49e/img4.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 20.726495726495727%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsSAAALEgHS3X78AAAAnUlEQVQI113PSQ7DIAwFUM4V5mAwQwYaSIdIvf816rSLSpHe/ztbNpsy7BVvi9/WkJPTWhujpVRcyIvhiwv1a8LA2baGZ8NXj4+GvYZWQ8TRgacQS4EAbkRvnTMeLAGwxhomlVonf+zx6PHYE/X7kXrFlDPGdKLkUnKYs0vRlXSaC9A8mxLcN6TLtyXU2dPNSinaOHDBuRj+5AU98gHBhUAhdRdZ1QAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img4\"\n        title=\"\"\n        src=\"/static/d836f12eedc71eca7c971d867e21f632/40fad/img4.png\"\n        srcset=\"/static/d836f12eedc71eca7c971d867e21f632/707e9/img4.png 148w,\n/static/d836f12eedc71eca7c971d867e21f632/649e0/img4.png 295w,\n/static/d836f12eedc71eca7c971d867e21f632/40fad/img4.png 590w,\n/static/d836f12eedc71eca7c971d867e21f632/b3fef/img4.png 885w,\n/static/d836f12eedc71eca7c971d867e21f632/4d49e/img4.png 936w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 4. OpenAPI generator plugin configuration example</em></p>\n<h4>Retrofitting OpenAPI</h4>\n<p>Despite the vast configuration possibilities offered by the OpenAPI code generators, in order to integrate OpenAPI into our AUTO1 development world seamlessly and make the generated API and models classes adhere to our coding standards we needed to extend the default Spring generator that we’ve decided to go with in first place, to include some AUTO1 specific code structures.</p>\n<p>To be more productive, concise and spare some boilerplate code we use in AUTO1 the Lombok library, a lot, hence we couldn’t afford not having the Lombok annotations included in the generated classes as well. To follow the OpenAPI generator practices, we have added a configuration property to control enabling and disabling the usage of Lombok annotations:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/999410ded404fcb4052d4d6c6aa41d84/01d07/img5.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 18.580375782881003%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsSAAALEgHS3X78AAAAmUlEQVQI11WObQ7CIBBEey0FCgX2g9ZELa223v8gDqkxmsyPYcPbfd1W9TnLcuP1LjkNIfgQgrHubOyR1q07GUz+YlzfXUoCti9txWst2LVVEc4sqlq0jJmEhaeSiiARZdRWvPcdHgAAb0jVxyzINFJMBJ5FUDJl/BaKwpEoKkemocE4i5v12rRVYu+btnX91/zQ/tj+FGi/AQntQF+49FjdAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img5\"\n        title=\"\"\n        src=\"/static/999410ded404fcb4052d4d6c6aa41d84/40fad/img5.png\"\n        srcset=\"/static/999410ded404fcb4052d4d6c6aa41d84/707e9/img5.png 148w,\n/static/999410ded404fcb4052d4d6c6aa41d84/649e0/img5.png 295w,\n/static/999410ded404fcb4052d4d6c6aa41d84/40fad/img5.png 590w,\n/static/999410ded404fcb4052d4d6c6aa41d84/b3fef/img5.png 885w,\n/static/999410ded404fcb4052d4d6c6aa41d84/01d07/img5.png 958w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 5. OpenAPI generator plugin configuration example enabling lombok annotations</em></p>\n<p>The above setting would result in generating the following annotations in the output classes:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/e1b5aba9483f31ed3b66d79733319b8e/7a5fe/img6.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 253px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 65.61264822134387%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABoUlEQVQoz4WS+Y6bMBjEeZ3GxhcJZ3BYHJssMYQ7pbnarVr1/Z9gzbJdVdpEHY3mD5vx97OF1aTOr5wMgjVPOArDqj2Ol5dhvMosV1L6S4zwJPI3ySw6yRIRqxKqIpoGxHVXT0Jmu71Uu2jNeRzHYeB5gR+Gnh8EgUl/uZq0dBzTt1rh/NHkuKVNgqMoqrpxvP4cTje1y/OyPv343fRj1RqUl/7rpRm+PetKSpWuXYSJxRh1GDX5jvImw8XMEqVzzphzvq/P2BjT2WaLMfpJhD6W9f0Gr+cvQwfTFC0AM0d8nPVfWWWB9jkUKTLleI3XkXlzc3m8Wk0g91j+KQ8d6N/c1PA4gLKwywKeTwvfIwAw22aG4mG5bWDfgvzZ/vA+n1JuUaYMlL3h+NEVprIZaL7WGpqZWpsmzJSttsj0Cw3DED8abp1PdtcBqey2AXUF6xoeDvBQQp0DvV8UGrQ18Lz7w63dlqgNFtyYKIFlMllwxDeuVFEqgk2ycj2P3GtbbUJvGe1T84dR7tIhpVfF6hjxRDTDRR/6IOQxTzBCn5/+FZNLsCJTQJfiAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img6\"\n        title=\"\"\n        src=\"/static/e1b5aba9483f31ed3b66d79733319b8e/7a5fe/img6.png\"\n        srcset=\"/static/e1b5aba9483f31ed3b66d79733319b8e/0376c/img6.png 148w,\n/static/e1b5aba9483f31ed3b66d79733319b8e/7a5fe/img6.png 253w\"\n        sizes=\"(max-width: 253px) 100vw, 253px\"\n      />\n  </span>\n  </a>\n<em>Fig 6. OpenAPI generated lombok annotations</em></p>\n<p>Sometimes the need arises to define custom annotations but only on some specific classes, for that purpose we have introduced a new attribute to our OpenAPI specifications called x-extra-annotation, that hints the generator to add additional annotations to the generated code. For example:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/3b7200d2b5bdbad6cc73dd1ec7eea01b/c59f2/img7.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 42.0654911838791%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAA90lEQVQY011Ri3LDIAzLdwX8DGBIclm3dv//KzOkbbbdCSL7EJHMFAC3hHfDW6GibIpZsAgQ8RwhRgwRQsQ4EJ4YHcDJxUhcitQqLdPWZK26b5ps0Sy8iDZVE04sjixiKl5mjjjESngYW7P981H3g0SAGZlI2A2QOiEvO6Tzsxx/jpgEvxseme6rWNIQ3C2cPv3EfxKvzuTL9XXBR8WPgqsl1gwos2dzAUC4ol6x+12n2AHQlTfjVu3r2Pfmd8DSHTIrk0fwXQf3ziJP22+xMo5Rgwm4EUdysYr6R31anfjeR5i1i+NL3M0DzsPS/Asx/rH9fqoz8w9383gSSHlPaAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img7\"\n        title=\"\"\n        src=\"/static/3b7200d2b5bdbad6cc73dd1ec7eea01b/40fad/img7.png\"\n        srcset=\"/static/3b7200d2b5bdbad6cc73dd1ec7eea01b/707e9/img7.png 148w,\n/static/3b7200d2b5bdbad6cc73dd1ec7eea01b/649e0/img7.png 295w,\n/static/3b7200d2b5bdbad6cc73dd1ec7eea01b/40fad/img7.png 590w,\n/static/3b7200d2b5bdbad6cc73dd1ec7eea01b/c59f2/img7.png 794w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 7. OpenAPI specification with java annotation hints</em></p>\n<p>The above attribute would result in generating the following annotations in the output classes:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/15371fc83c17be4c4f2aed0b11ea5ead/49981/img8.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 239px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 80.3347280334728%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAAsSAAALEgHS3X78AAACXElEQVQoz3VSaW+jMBDlr6y23Sox+MoBJQZzmJsEDIHcaqr2Q///L1i7h9R2u08jCwa/mTdvMI4HUm/aaiPDOIlEtmn7tGiTBI1bsFmb69psGx29NFcrhBD5DGMcaFa2WdnwUISxKOtN3QxRSDsJqtJKE7iurTy3VBXHwRh/JT89Ts7H221/E4XT6QSaJrBMy7IIANSydJjm+/mNqcnNxlTlA449D9s2WS5V0OUCLxaYUvI5/oXx/DQZB3DYTfsOnE/Tba/VXh8mVWmqbghRCCkmP8PopKluF7nFfRRFUAgYhfr0PcRWSJ0eQ/P5f8iqyeU0UWZuO3A6TpUxwxbI1lTllBbtcwfUtPin7sZ+BHVpJQLWlVVkylWt4i2yDGYpqkooYjSj7/hK3k+z3EozvQ8pwXoN8sIsSjPPlZGTNLkrsruAT9UKIIQIQqxs+ChhbAPymJKek44TNkPSw7uQjBz6rtuOVzk87s4vdbOX28NweBiP1yjOgzB+27iRMhLfE9/BwqOuPfcdkjAqVtR1lsznSRqlec4Dnwfc5yrBXNd2nHvyaoEBMZkAGkbgcmXD+aUdT35SVXK3O12zou7k74fLr17ejcPtMNzsdzeH/R8ILYT1j2qoAZSE5RwLfx4zm7uLxJuvnGWUZCLNPd+LBVsu5p57zxkLPT9gnmuzlcOUhQbSJhBuk6PAMoBHga45Sl1YrGXdyKJq95fnIEjqqBvKyyYZyqipRd+ljWdTIxKpml7Z97ZMFQjriZSrCvg1pdWphEphnXl1S1M0WamnnzD79jibfbzMvnyk9C8cUt6lafmjgAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img8\"\n        title=\"\"\n        src=\"/static/15371fc83c17be4c4f2aed0b11ea5ead/49981/img8.png\"\n        srcset=\"/static/15371fc83c17be4c4f2aed0b11ea5ead/a9a92/img8.png 148w,\n/static/15371fc83c17be4c4f2aed0b11ea5ead/49981/img8.png 239w\"\n        sizes=\"(max-width: 239px) 100vw, 239px\"\n      />\n  </span>\n  </a>\n<em>Fig 8. OpenAPI generated extra annotations</em></p>\n<p>Generics is a very powerful feature that is available and widely used in the Java language for almost two decades now. Unfortunately, as of the time of writing of the article the OpenAPI does not support out of the box <a href=\"https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/\">inheritance</a> together with generics. To make it available to our developers we have introduced yet another attribute in the API specification named x-make-parent-generic specifying the genericType sub-attribute that indicates a generic type of the parent class (in this case PageDTO):</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/5ad75253da4419e8bcbaa02dd019b052/356aa/img9.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 57.06214689265536%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABMElEQVQoz4VR2Y6DMAzkb0qSScDk5Aq09JL2//9nDVRtH7ZbK4cVZTz2TKGUSqQuEeeI0eEcMHkdajVaOTaiI0mVllL9GcV+RcI94RTUNYELZYeld5fcHofoGpIf0DsYvVU/HWavE2GyGKyumpD63I1zY72U4oXY6uzVCj5thSXgGIzWphT8EbxkKcrDgRfTQuEJA8CJNoBGsT0rATM4zeTJVoZ06KjNPmUfRxcH67sm9E3Kax56287re22rtW3GQ5tAmge2tQndOC3XYZpNZaR6mxZq74A5XzNvjbDmuLer1DnS0lvv3OPv53iAOcgonpzx2apbZCF0yQJ8cundKsYLuSo3ezZMZ7Y6eGoc1fSNecv0ho9b81RXach5Oub5BOAL81MRblPsTgqOkvc/zL/yGnlteNMNWAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img9\"\n        title=\"\"\n        src=\"/static/5ad75253da4419e8bcbaa02dd019b052/40fad/img9.png\"\n        srcset=\"/static/5ad75253da4419e8bcbaa02dd019b052/707e9/img9.png 148w,\n/static/5ad75253da4419e8bcbaa02dd019b052/649e0/img9.png 295w,\n/static/5ad75253da4419e8bcbaa02dd019b052/40fad/img9.png 590w,\n/static/5ad75253da4419e8bcbaa02dd019b052/356aa/img9.png 708w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 9. OpenAPI specification with java generic type hints</em></p>\n<p>The above attribute would result in generating the following generic parent in the output classes:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/c7fb92492525b6a3a247d7599896a9d1/ac3f0/img10.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 414px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 19.565217391304348%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAIAAAABPYjBAAAACXBIWXMAAAsSAAALEgHS3X78AAAAj0lEQVQI142N0Q7CIAxF+SNpCxOGwBhOxxY2NdmD//8jIpvvnrTNze1tysygpBZISBJJYBHwN0ycSUdlU2vuWgdlrqq4nPMyoPahKvwH1ABrjBTN8a44iF+Jlf0G9yIqWyJBFagxtkXIDvoWFs+TBed8jIPvgrXeujBe4BVgcacY++f2TnNO83obpyk/rO8+KvczSO2jpckAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img10\"\n        title=\"\"\n        src=\"/static/c7fb92492525b6a3a247d7599896a9d1/ac3f0/img10.png\"\n        srcset=\"/static/c7fb92492525b6a3a247d7599896a9d1/441d5/img10.png 148w,\n/static/c7fb92492525b6a3a247d7599896a9d1/51e4c/img10.png 295w,\n/static/c7fb92492525b6a3a247d7599896a9d1/ac3f0/img10.png 414w\"\n        sizes=\"(max-width: 414px) 100vw, 414px\"\n      />\n  </span>\n  </a>\n<em>Fig 10. OpenAPI generated extra generic parent inheritance</em></p>\n<p>Yet another limitation of the OpenAPI generator, was that the key of a generated Java API Map needed to be of type String, this was something that was not sitting well with us, hence we’ve extended the generator to support custom key types via the x-map-key attribute like so: </p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/7b004dd68dfa6c4824101f30b8f6a9b6/12f07/img11.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 40.55944055944056%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAIAAAB2/0i6AAAACXBIWXMAAAsSAAALEgHS3X78AAAAy0lEQVQY05WQC2rDMBBEfa5K+5nVz3aciKSB3P8mWdmlkEChgdGyGvS0I02Xwt8z3xonQyCOn2hSGeRjlTkpEX2F+AEcImPne0Nb1jqfWBEi/Qv2RTzgrSXLJZeCZAIVQFRHr/h7MnExj823xdSKwtx9eXzk8C7y6v50nPBv2you5205nc2QlUWUoWpD4sKoejhp3yr22MTXxveZ7y1umXsd/VohBmRDgZ92BhmHrNi48YBdBc5Lq7n3a+9dfK64LyHQ0G/a0f84HvsJqAh39WRMt/4AAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img11\"\n        title=\"\"\n        src=\"/static/7b004dd68dfa6c4824101f30b8f6a9b6/40fad/img11.png\"\n        srcset=\"/static/7b004dd68dfa6c4824101f30b8f6a9b6/707e9/img11.png 148w,\n/static/7b004dd68dfa6c4824101f30b8f6a9b6/649e0/img11.png 295w,\n/static/7b004dd68dfa6c4824101f30b8f6a9b6/40fad/img11.png 590w,\n/static/7b004dd68dfa6c4824101f30b8f6a9b6/12f07/img11.png 858w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 11. OpenAPI specification with custom map key definition</em></p>\n<p>The above attribute would result in generating a map with a custom key type, as depicted below:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/226341eb51cab1bfc49da67dd191bcdb/4c1fa/img12.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 410px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 55.36585365853658%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsSAAALEgHS3X78AAABHklEQVQoz5WSy5KDIBBF/Z7xAc1DEBCHaKKCoib//y9DzGI2UzUJRVG94Jy+BZ3pvqk1AwDCCHy4MlZTNYh2VvoqKaeAP4Gl4VwxQoBQIB/2zu5H5eey5hhjwGfb9xXZvhVxLYIv56l8WmpcVW/DjyO/H3lcii0WS3gqprHkZ5A3Yt/LEIp9L2KCl2Ke8jV82bZE6H86Wx3EDrsGgoXQ4tvsl7iPPmhjMf59iL9hisFZlGKHuQxj7nrTXb/bXtlB20GpTrROU8pQSoIxwuhlRKc0qxBoAbHHj6G6Oe7jMXoftt2vsR8u1nXLdoxzMMZezBAusVVON2wyYET650alyRAM0uaECNkIIV9nzTljTJ41pbRmQvKGp6miINNlCj8F3oveXsuroAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img12\"\n        title=\"\"\n        src=\"/static/226341eb51cab1bfc49da67dd191bcdb/4c1fa/img12.png\"\n        srcset=\"/static/226341eb51cab1bfc49da67dd191bcdb/cd363/img12.png 148w,\n/static/226341eb51cab1bfc49da67dd191bcdb/45083/img12.png 295w,\n/static/226341eb51cab1bfc49da67dd191bcdb/4c1fa/img12.png 410w\"\n        sizes=\"(max-width: 410px) 100vw, 410px\"\n      />\n  </span>\n  </a>\n<em>Fig 12. OpenAPI generated map with a custom key type</em></p>\n<p>As mentioned at the beginning of this article, our Java services are using the Spring Cloud stack with declarative Feign REST clients, originally the <a href=\"https://spring.io/projects/spring-cloud-netflix\">Spring Cloud Netflix Feign</a> module was used for that purpose, however, with recent upgrades to newer Spring versions it was replaced by the <a href=\"https://spring.io/projects/spring-cloud-openfeign\">Spring Cloud OpenFeign</a> module that resulted in changes in the imported packages (import org.springframework.cloud.netflix.feign.* was replaced with import org.springframework.cloud.openfeign.*). So we ended up in a situation where services use either the old Feign clients (located in their respective packages) or the new ones, yet share the same OpenAPI specification. To support both cases we made it configurable for consumers to go with either one:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/78a40c0a233e8d409fc7e7a34f5db8bf/12d02/img14.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 12.253829321663018%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAACCAIAAADXZGvcAAAACXBIWXMAAAsSAAALEgHS3X78AAAAYUlEQVQI1xWMBwrDQAwE/TOfmlVOl+CaGP//H1FgGAYWdgqjz6bXpueq+3tBbIQNAJCYWMpVgEgIwlATExQVrc2TCj6nfXe7j7KuLwnD7hI5eqZHL9Q8nUfndMrgEf+urx/0PSGb9SaRqwAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img14\"\n        title=\"\"\n        src=\"/static/78a40c0a233e8d409fc7e7a34f5db8bf/40fad/img14.png\"\n        srcset=\"/static/78a40c0a233e8d409fc7e7a34f5db8bf/707e9/img14.png 148w,\n/static/78a40c0a233e8d409fc7e7a34f5db8bf/649e0/img14.png 295w,\n/static/78a40c0a233e8d409fc7e7a34f5db8bf/40fad/img14.png 590w,\n/static/78a40c0a233e8d409fc7e7a34f5db8bf/b3fef/img14.png 885w,\n/static/78a40c0a233e8d409fc7e7a34f5db8bf/12d02/img14.png 914w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 13. OpenAPI generator configuration with OpenFeign code enabled</em></p>\n<p>Another shortcoming of the default OpenAPI Spring MVC generator was the lack of support for making the generated classes implement specific interfaces (using the “implements” keyword), as of version 4.3.1 only the “extends” keyword was supported. We have overcome this limitation by introducing a custom attribute x-codegen-implements allowing us to specify a number of interfaces the generated class should implement.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/d754a293adc061f24006aedefc0d5199/d8b42/img15.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 71.93548387096774%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsSAAALEgHS3X78AAABh0lEQVQoz41S23KjMAzNz2zji2wDxvjKnYSk7e5D//9jKkOzaWfSaTViEGaOdI6OD0GL1cHZwdWLkxOmUozxX8aBcggV38D84iUIiadwix/AUgjKeFfzlygJKMoYo5SQIzkesfgBvE8QgHgYG1EpUWrjY+tCwoLmZnzPB2B8BACSbwr+L0Ey0ju/LKdxnINtFLBC8lLyQj6QcBBCbNrAlnk4Kn91ZNFPQf159fTi4a0TzwFGV+A64FN8TP7/jY1CbKdlje3QDlPqRzwjOSjdFkFztSe9a76hwcc0n59j28VumE6r81Hr2lpfVRX2baxvrDONqxt7p70TwdKHOM2nEFNM3Tif234MqZuWC2KwVz8t2BNPQuw+wHlhDEYDfyOsls01uVjWG4kWIsPsHM22oXlZwJ6U3CeLbWeD4WtQXKq8CM43y7NX+3rga3yhjX/RlaERUYtSCVWUSBVT14Z9c2PvYHwRBqbgVw9RS21santUiLI3kvD4knwmgzqPKJVlyuTm0nfX8x2Rk5x4tpj1LQAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img15\"\n        title=\"\"\n        src=\"/static/d754a293adc061f24006aedefc0d5199/40fad/img15.png\"\n        srcset=\"/static/d754a293adc061f24006aedefc0d5199/707e9/img15.png 148w,\n/static/d754a293adc061f24006aedefc0d5199/649e0/img15.png 295w,\n/static/d754a293adc061f24006aedefc0d5199/40fad/img15.png 590w,\n/static/d754a293adc061f24006aedefc0d5199/d8b42/img15.png 620w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 14. OpenAPI generator configuration with custom interfaces</em></p>\n<p>The generated output for the above configuration can be found below:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/39a7721d4f479f53c1a0c726480ea48c/3abe0/img16.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 447px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 48.32214765100671%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAAA6klEQVQoz52QwXKEIBBE+SCJDDOI4mKMiiKg+f+/Cex6SE4pd4pDz1R1vW4YtSQEgIQ3hrVjY73RVoG4b1YdyrewxZxipXWBIkLOL+7w2fdZnWcVAz9SRXSvPMvOI/EUud94DNXqquzP+JICXuKaX/K5CmD7+pECTzsPnu87xkC9kSRBEzQIRKhUQ0hESiKqpsmCVH7lwIKFQYN/wNyKcV6dT5Ptoq3PTxke9TSYfFmcX9zaG7OFY1n97PbJjcMXMvmEaBSKUOu2IDBLvIL+6fi6wVUidzaqYAvEdssWpsWN/TR0Roj63w/7AXQofA9Nj9+NAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img16\"\n        title=\"\"\n        src=\"/static/39a7721d4f479f53c1a0c726480ea48c/3abe0/img16.png\"\n        srcset=\"/static/39a7721d4f479f53c1a0c726480ea48c/64cd7/img16.png 148w,\n/static/39a7721d4f479f53c1a0c726480ea48c/5cfb8/img16.png 295w,\n/static/39a7721d4f479f53c1a0c726480ea48c/3abe0/img16.png 447w\"\n        sizes=\"(max-width: 447px) 100vw, 447px\"\n      />\n  </span>\n  </a>\n<em>Fig 15. OpenAPI generated class implementing custom interfaces</em></p>\n<p>Teams often run into the situation where they have to use multiple OpenAPI specifications to generate the necessary contracts to communicate with the upstream services. Unfortunately, the org.openapitools:openapi-generator-maven-plugin requires a separate configuration for each specification, which was a bit too verbose and too cumbersome for us.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/8de608c76b8f364a46c3b5bc27945bb0/b0a39/img17.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 110.11699931176875%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAIAAABPIytRAAAACXBIWXMAAAsSAAALEgHS3X78AAACdklEQVQ4y41UCXKjMBDMb2JACN0HCIG4bOeq/f9rtmXjjZN1andKJSjQaHp6euZJCbolFVtujHZtsK51Tvet1JLXNW1ozSihNake2VNZVr3nb4uZorW+M9ZZ65y3xhiltNWsNQ0C4NgDZ1IRSknwfAySNQ2ldU3K5+eiKEoY9stL9TgyIQS/4A9nADY5ImNNTXaryGPIN2c8EG4d5BSlUFoIbmRd5bDFAeuwo7hZVdyQ7M7Y+5a/LuaYZOdzsgqZO9M5GTvtjKgppU02gOKsvsJ5uoLDVfj6vpq31czLHIY5pXRe+9djjL031kogkgrmdNNaBv4RfnfGNYLVx6S3EeBN19psmnWWeg0Sq8PhkNPIVh5uuD9hY+8sW0e1DHIdBAAbH2xerfeu83oMIrYiOG4U/cw5U1oRXN059r7Zl8UA/zZ3MS1DmvshrZM/Txq4Xhbk35Tf2EbCQ8dfZgXOkZJVDKz8VN7vpZqjfFshLgVJomyHn4XxwPkiEoXctNZGQ2e7Mshl/cMZW2cbkMGFlkqiHvhSVjl6eX3cGfk7MkjeRoUmQc1oFjkUQdFSgtcNJfca+6Kwq0jg876Zj6Nd16kf5yFN2zKcN8gl9KHVBn1mIX1vePT8SuensxT018nBf136PsL6KSq0evAMXV2W+UxZ7aIgX+pMCEhGtU65nuqUZGgh8DbPBu8hNmdl37LgWAeRyOYO9uUyiCRPlVHNMVc7RQeFxHEKcZxHBwj4dUoQCfvTpDthRVkJnucRIqcgx05AJ3kKYTUQDOVNfe2nOjfr3RgCmSkI9BPmieRoGFI+sOq+k3dno5rzrFFhsF1c5s5/SGu33z+Y/g5lFP7GAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img17\"\n        title=\"\"\n        src=\"/static/8de608c76b8f364a46c3b5bc27945bb0/40fad/img17.png\"\n        srcset=\"/static/8de608c76b8f364a46c3b5bc27945bb0/707e9/img17.png 148w,\n/static/8de608c76b8f364a46c3b5bc27945bb0/649e0/img17.png 295w,\n/static/8de608c76b8f364a46c3b5bc27945bb0/40fad/img17.png 590w,\n/static/8de608c76b8f364a46c3b5bc27945bb0/b3fef/img17.png 885w,\n/static/8de608c76b8f364a46c3b5bc27945bb0/301c0/img17.png 1180w,\n/static/8de608c76b8f364a46c3b5bc27945bb0/b0a39/img17.png 1453w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 16. OpenAPI configuration with the default openapi maven plugin</em></p>\n<p>In order to circumvent this issue we have created our own maven plugin The main aim of it was to reduce the boilerplate configuration, yet keep the flexibility of the original plugin:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a8f9327908550a8523a8deb230d330fa/5d76c/img18.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 69.0625%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsSAAALEgHS3X78AAABi0lEQVQoz41T7XKDIBDs20QRETgO0USN+JHaj/d/nS7VJDP90cnOCSgue3unb1LKvrXjxXomrhv2IdSuDdZqVamylMU/eMOlq/Jj4q8l9MPF1wHsENiT8gSyyLIcyHLxCCHuZChjIqOWgbZIc2/XK8XOsCPncUbN7D27ttZ7NF57l1ISOxnI8mI42+/Vb9Fvk7+NNE/DtG7XaYlx+Fzq78V/zP5r8evokOlTeUfsCHtbZLy6RTdeu36cu2Fszl3fd/M10W4j30Y39eSsQvKJnIsisIbt98jzQIEVk0KpkrsEzFgXx00uYHSv5KFcyMK7Cqd2rTXWaUPGEjn4ZksYnbWEBVPyrFTSe5LFrziUL43R2hpjtQGeC63xHPdIqlL3Bh7VBpo6Zb5eGYCsIcAhkAE0jYU27cqVkuLeZ1lVEjX4nHx/tkg+uYWz1N00HnE3/LfPSKNvzXt0ZMrTKZXnsf0/Ds+nTNRcoVsO3wS7wJV8kQwZT6nUfWOL37NKKV9V7hqDH4NdJV/jPPADTk6hFlbPAUQAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img18\"\n        title=\"\"\n        src=\"/static/a8f9327908550a8523a8deb230d330fa/40fad/img18.png\"\n        srcset=\"/static/a8f9327908550a8523a8deb230d330fa/707e9/img18.png 148w,\n/static/a8f9327908550a8523a8deb230d330fa/649e0/img18.png 295w,\n/static/a8f9327908550a8523a8deb230d330fa/40fad/img18.png 590w,\n/static/a8f9327908550a8523a8deb230d330fa/b3fef/img18.png 885w,\n/static/a8f9327908550a8523a8deb230d330fa/301c0/img18.png 1180w,\n/static/a8f9327908550a8523a8deb230d330fa/5d76c/img18.png 1600w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 17. Reduced OpenAPI configuration with the custom openapi maven plugin</em></p>\n<p>We have decided to keep all of the OpenAPI specifications in a single place - a GitHub repository. In order to have proper release management and versioning of the OpenAPI specs we have made it part of our Jenkins pipeline. Upon a merge to the main GitHub branch we pack and upload the changed, and automatically versioned, specification to the artifactory. From there, it can be simply imported as any other maven dependency and used to generate the required API classes.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/7124827bd997165a9dcd229f30eb99f9/af01c/img19.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 64.93506493506493%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABl0lEQVQoz22Si3KsIAyG920qoosKqAvKJYBob9OZvv+7NK7dntPtZjAThD+Bj5xKSo1qX6L8WIfX2GeQ7FwRQsqbkZKS3ePGezvhV5S0berF8ecgN5CYi7GWNe2ZoTW41DU1elo+ElcVLff0NMy7fvVyic5DnKwPTm3QvaV+C7Kuq4KU9+JDf66rZHl2YgORQFmIs4FgxwDTAuMKwulOctxV/a58jbB4NCJ7AWA8wJaMs5eO93K4cCEGwfTQqKGpqz+VG1bbK7aX2Ac/9aIdRS15jcBoSegOjiJE8vfYuCC68+c2vqUBgc0XVhTF01NRXJkTQvGqGFW3M95Xxpsg0uQEFo+Wz8bq2Y5KazV63SQrYO4Oro9p4/9o+AoS2axRxSW7kGfrIPjs++wlplicGCQ7JP/Ex+GD4Qg8+SHEkLcNsc3Wq8mqSU+Iz2jMjgMmjo2E+7/vXF77BFHje6Jm0mNy3Khuz7zn/vb0iG6vdTpQ4zN85OE97x3qpxYXfxqU/u6s/+e7+NIjEo5IrG5xMFaRB438wL4ApXOao9LJCLkAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img19\"\n        title=\"\"\n        src=\"/static/7124827bd997165a9dcd229f30eb99f9/40fad/img19.png\"\n        srcset=\"/static/7124827bd997165a9dcd229f30eb99f9/707e9/img19.png 148w,\n/static/7124827bd997165a9dcd229f30eb99f9/649e0/img19.png 295w,\n/static/7124827bd997165a9dcd229f30eb99f9/40fad/img19.png 590w,\n/static/7124827bd997165a9dcd229f30eb99f9/b3fef/img19.png 885w,\n/static/7124827bd997165a9dcd229f30eb99f9/af01c/img19.png 924w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 18. Example OpenAPI specification import</em></p>\n<p>Once having the OpenAPI spec dependencies defined in the pom file, our custom maven plugin iterates over the all available OpenAPI specs and generates code for them, honouring the defined configuration.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f10a02f1ddb743b1299bdff64086a94b/5d76c/img20.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 51.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAAsSAAALEgHS3X78AAABJElEQVQoz41R2ZKDIBDM3wQQBbnFKCpqzLX//zk7iLWbl63NVNdw9nQznCil26S/rm4aL67xzlnvndXcqFJwijEmhEACIHxMCClynIDMWRl7eY9qDWIJEnJjpdROaauN1Vo7K1rL/Q6nuajLg1xSinDRuvoxm/tsbpN+zHqZ2rhu43wdpnibm9dqnssB4BcFzeJJGfx0vs5nUOK1mHn0/RDDGNsutF0XB7+Nahv1dVDroC5NDZK7ckl7L+7RLEFlb+BCigrKl3AGSJGGtKIJrEpbh+3gk+fG8PMZI4TPiODdVUo/zcnLPHlvGEJEiSr2QmuplIGWQO031p9xAntwNXgx95IzVlUMmp9d/U+WkoVW9D69c/9D8onmQTaSATAuPqb8xjdB3XON5TSU2AAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img20\"\n        title=\"\"\n        src=\"/static/f10a02f1ddb743b1299bdff64086a94b/40fad/img20.png\"\n        srcset=\"/static/f10a02f1ddb743b1299bdff64086a94b/707e9/img20.png 148w,\n/static/f10a02f1ddb743b1299bdff64086a94b/649e0/img20.png 295w,\n/static/f10a02f1ddb743b1299bdff64086a94b/40fad/img20.png 590w,\n/static/f10a02f1ddb743b1299bdff64086a94b/b3fef/img20.png 885w,\n/static/f10a02f1ddb743b1299bdff64086a94b/301c0/img20.png 1180w,\n/static/f10a02f1ddb743b1299bdff64086a94b/5d76c/img20.png 1600w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 19. Example OpenAPI maven plugin configuration</em></p>\n<p>When dealing with a large number of microservices a common case is that they share common features, hence common models, within a single bounded context. In a typical Code First approach operating in the Java ecosystem this problem would be solved by sharing a common jar which would be then included by the interested parties. We were interested in achieving similar functionality but from the OpenAPI perspective. We achieved this by simply extracting part of the common functionality into separate specification files <a href=\"https://swagger.io/docs/specification/using-ref/\">and referencing</a> them from the individual OpenAPI specifications that would like to reuse these definitions.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/4018acea1cacb761c14c212fb99b3e8b/71150/img21.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 65.57632398753894%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsSAAALEgHS3X78AAABKklEQVQoz5VSW47CMAzkNts2dh6N82wpoAWk3fsfaCcFJGDFR62RZTUZjz3N7hj4t1LxPPSDUsCG2DHRQVgsK22YeRt5UFRHumR92lfv/dD3G8hEpKjxFw9tS8Tbxk6Ozol+qr4sOUff9f3wId7JkI2O9sLY2mBxzcbYF9hHthZK72MPimfhYzbL93VeDjHllCsQUJSaS6vXYkKXZ/11Z0Vi6BBoEoPe3VfXd694xPDfsFtg7ckzpnfBOrHOr1msHQ26q9VX9YkMw53Ruu2mbzdhfPOe1Cfz7mRcOUW6FhNzkRBZt0DGs9HaPKDvhcFz0k9kKMNzr0ZnvATwRYJfEWKUkIC1ThITjHSjh/iL8jlxTaFMexjb7M3N6jLNpc7I+IijdlrnOi34c3/dyo7FFW5PJAAAAABJRU5ErkJggg=='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img21\"\n        title=\"\"\n        src=\"/static/4018acea1cacb761c14c212fb99b3e8b/40fad/img21.png\"\n        srcset=\"/static/4018acea1cacb761c14c212fb99b3e8b/707e9/img21.png 148w,\n/static/4018acea1cacb761c14c212fb99b3e8b/649e0/img21.png 295w,\n/static/4018acea1cacb761c14c212fb99b3e8b/40fad/img21.png 590w,\n/static/4018acea1cacb761c14c212fb99b3e8b/b3fef/img21.png 885w,\n/static/4018acea1cacb761c14c212fb99b3e8b/301c0/img21.png 1180w,\n/static/4018acea1cacb761c14c212fb99b3e8b/71150/img21.png 1284w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 20.Example specification reusing “common types” stored in separate definition files</em></p>\n<p>The challenge in having OpenAPI specifications referencing other OpenAPI specifications was the dependency management and versioning part. It was especially hard from the consumer perspective, since consumers would need to include both specifications in their respective versions (the referencing one and the referenced one) in order for the code generator to succeed. The solution for this was building API bundling, by adding to the specification a meta attribute called x-bundle-apis we could make all referenced definitions to be included in a single final artifact that was then uploaded in the CI pipeline to artifactory. This meant, from the consumer perspective, that they just needed to include a single dependency now, making their life significantly easier.</p>\n<p>By default, when bundling the API we’ve always included the latest referenced specification. Since we have had cases where we wanted to give more control to the API owners to specify which concrete version of a “common API” they would like to reference, we’ve included another attribute x-bundle-imports which could be used as following:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/e0131fcee2a3f97574b40e2ae71c06b3/3441b/img22.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 79.49921752738655%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAIAAACZeshMAAAACXBIWXMAAAsSAAALEgHS3X78AAABd0lEQVQoz5VS2Y7bMAz058QSaYm6JTvKpt2g+9T//5yOFaQ5WmB3BzQxkDUUr6k53f188nN1FGKagcMOpZT+DFMUPm/t/bR9/OillFzXum7b8WSM/VQ/WcO11LZuZe0xZdC6Ho/97UviHvj3qnvkw2FW6mrArL+ASWuqQlmYjeA1/R3s4mDpo/KllxQ8WvUNMTMpTcnSW9AislhBuJvB0d3+Hj6Ks6NL5l/V/Gy+5YDiMS58an7FSwsnhJSF0DZn2JjFWutC9D46H8R5v/PgQxg+GiuP+omIFHESPqVl62dMGKuCmcHj9pUMn+FF3JMYaaMSt9B7IugN00j7/3hNmwaQfHNUHQbGEowEK95ItFdyfeDWs3/FGmVTC8tep/d0u0z3y0qrYXrfoUEeX/Z0TgytuLCP6BaUiOkZjEyIn8Q90qVwCRJzzbVhzXOBx+KvWPhSN+w9OH6VhpMVqru4CB0DRS9Dk+NoL1qNKMNqSDuJ4GmP65z/A+qusDEcXT6KAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"img22\"\n        title=\"\"\n        src=\"/static/e0131fcee2a3f97574b40e2ae71c06b3/40fad/img22.png\"\n        srcset=\"/static/e0131fcee2a3f97574b40e2ae71c06b3/707e9/img22.png 148w,\n/static/e0131fcee2a3f97574b40e2ae71c06b3/649e0/img22.png 295w,\n/static/e0131fcee2a3f97574b40e2ae71c06b3/40fad/img22.png 590w,\n/static/e0131fcee2a3f97574b40e2ae71c06b3/b3fef/img22.png 885w,\n/static/e0131fcee2a3f97574b40e2ae71c06b3/301c0/img22.png 1180w,\n/static/e0131fcee2a3f97574b40e2ae71c06b3/3441b/img22.png 1278w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a>\n<em>Fig 21 .Example specification using pinned versions of “common types”</em></p>\n<h4>Challenges ahead</h4>\n<p>Despite the vast number of improvements we have already made in order to retrofit OpenAPI and its tooling into our existing AUTO1 ecosystem and practices, there is still room for improvements. Just to name a few. Although OpenAPI specification itself is language agnostic, we have language specific generators which in some cases need to be instructed on how to generate the output code, and unfortunately these generator hints often make sense only in context of a specific language. As an example, instructing the generator to make the generated Java classes implement some specific java interface. Since the OpenAPI specifications are enriched directly with these hints, it results in obscuring and cluttering the contract definition.</p>\n<p>Yet another challenge would be to generate more complex classes resembling the structure as seen when written manually in the times from before the OpenAPI approach was adopted.</p>\n<h4>The verdict</h4>\n<p>As presented in the article, OpenAPI is a very powerful tool that enabled our AUTO1 organization to move forward faster in a more structured and unified way in terms of defining and evolving the REST APIs. It’s not a silver bullet, as the design first approach and the OpenAPI specification come with a number of challenges by themselves. However, with the talented engineering force we have we were able to resolve most of them in an efficient manner, yet every organization planning to adopt this tool needs to weigh in the benefits and the shortcomings and make the call for themselves.</p>","fields":{"slug":"/openapi-journey/","tags":["auto1","engineering","openAPI"]}}}]}},"pageContext":{"slug":"/spring-5-indexer/","tags":["spring","spring-indexer","bootstrapping","performance"],"category":"Coding","author":"Mariusz Sondecki","date":"2019-02-05"}}