{"data":{"article":{"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"]}},"categoryArticles":{"edges":[{"node":{"id":"cb45e6dc-ba61-576d-adc2-ba609b0e722a","frontmatter":{"category":"Engineering","title":"Go 1.23: the new Pattern property in http.Request","date":"2024-11-26","summary":"A short review of the new Pattern property in net/http.Request","thumbnail":null,"authorName":"Andrei Gusev","authorDescription":"Senior Software Engineer at AUTO1 Group","authorAvatar":{"relativePath":"pages/go-123-the-new-pattern-property-in-http-request/avatar_Andrei.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAUBBgT/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAaeTsi2mDmfWV0wzf//EAB0QAAICAQUAAAAAAAAAAAAAAAECAAMEEBESEyH/2gAIAQEAAQUCtYiUvyE71ZkuQNuzaYvuQTP/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAcEAACAgIDAAAAAAAAAAAAAAABEQACEBIhMTL/2gAIAQEABj8C4KnprGnRhqdnHWpWKPH/xAAdEAACAgMAAwAAAAAAAAAAAAABEQAhEEFRMXGB/9oACAEBAAE/IRoFjyQHGILdksEYAYLXeSuANz4mvAhI736hHP/aAAwDAQACAAMAAAAQNBc+/8QAFhEAAwAAAAAAAAAAAAAAAAAAABAR/9oACAEDAQE/ECv/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAcEAEBAAMBAAMAAAAAAAAAAAABEQAhUTEQYaH/2gAIAQEAAT8Q0H5agDhjJzCu2+nxvKetJ9/fd9x0k6GIzjdTPf69KX9xcAAKlBR2wEDDmf/Z","width":50,"height":50,"src":"/static/479e481648d69b84a342bd2575a225d4/d2d31/avatar_Andrei.jpg","srcSet":"/static/479e481648d69b84a342bd2575a225d4/d2d31/avatar_Andrei.jpg 1x,\n/static/479e481648d69b84a342bd2575a225d4/0b804/avatar_Andrei.jpg 1.5x,\n/static/479e481648d69b84a342bd2575a225d4/753c3/avatar_Andrei.jpg 2x,\n/static/479e481648d69b84a342bd2575a225d4/31ca8/avatar_Andrei.jpg 3x"}}},"headerImage":null},"html":"<h1>Go 1.23: the new Pattern property in http.Request</h1>\n<p>In Go 1.23, a new <code class=\"language-text\">Pattern</code> property in <code class=\"language-text\">net/http.Request</code> was added.\nIt contains the route pattern used to handle the request. This improvement may have gone unnoticed by many, but it can\nsimplify request processing and improve performance in cases where we need to obtain the matched routing pattern\nfor the current request. Let’s see how we can use this feature.</p>\n<p>At AUTO1, we have more than 200 microservices, some of which are written in Go and include an HTTP server.\nIt is critically important for us to collect metrics from these services in order to quickly respond to incidents,\nmake data-driven decisions, and optimize system performance.\nFor example, we collect <code class=\"language-text\">http_requests_total</code> metric:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\">requestsTotal <span class=\"token operator\">=</span> promauto<span class=\"token punctuation\">.</span><span class=\"token function\">NewCounterVec</span><span class=\"token punctuation\">(</span>prometheus<span class=\"token punctuation\">.</span>CounterOpts<span class=\"token punctuation\">{</span>\n\t\tName<span class=\"token punctuation\">:</span> <span class=\"token string\">\"http_requests_total\"</span><span class=\"token punctuation\">,</span>\n\t\tHelp<span class=\"token punctuation\">:</span> <span class=\"token string\">\"total request count\"</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token builtin\">string</span><span class=\"token punctuation\">{</span><span class=\"token string\">\"path\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"method\"</span><span class=\"token punctuation\">,</span> <span class=\"token string\">\"code\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Obviously, for the <code class=\"language-text\">path</code> label, we should store the path template, not the specific value, for example, <code class=\"language-text\">/test/{id}</code> instead of <code class=\"language-text\">/test/123</code>.\nOtherwise, we risk a cardinality explosion, which can significantly increase the amount of stored data. You can read how to choose labels properly in <a href=\"https://prometheus.io/docs/practices/naming/#labels\">\"Prometheus best practices\"</a>.</p>\n<h2>Before v1.23</h2>\n<p>Previously, to extract the route pattern, we had to pass the multiplexer to a middleware function:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func</span> <span class=\"token function\">metricsMiddleware</span><span class=\"token punctuation\">(</span>next http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">,</span> mux <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>ServeMux<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\t<span class=\"token boolean\">_</span><span class=\"token punctuation\">,</span> pattern <span class=\"token operator\">:=</span> mux<span class=\"token punctuation\">.</span><span class=\"token function\">Handler</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">)</span>\n\t\t\n\t\t<span class=\"token operator\">...</span>\n\t\trequestsTotal<span class=\"token punctuation\">.</span><span class=\"token function\">WithLabelValues</span><span class=\"token punctuation\">(</span>pattern<span class=\"token punctuation\">,</span> method<span class=\"token punctuation\">,</span> statusCode<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Inc</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>In this approach, we matched the request to the route a second time, and that negatively affected performance.</p>\n<h2>Since 1.23</h2>\n<p>Starting with Go 1.23, we can get the route pattern directly from the request, without passing the multiplexer to the middleware:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func</span> <span class=\"token function\">middleware</span><span class=\"token punctuation\">(</span>f http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tpattern <span class=\"token operator\">:=</span> r<span class=\"token punctuation\">.</span>Pattern\n\t\t\n\t\t<span class=\"token operator\">...</span>\n\t\trequestsTotal<span class=\"token punctuation\">.</span><span class=\"token function\">WithLabelValues</span><span class=\"token punctuation\">(</span>pattern<span class=\"token punctuation\">,</span> method<span class=\"token punctuation\">,</span> statusCode<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">Inc</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>This significantly simplifies the middleware, making the code cleaner and more readable. We are no longer dependent on <code class=\"language-text\">ServeMux</code> and no longer need to re-match the request.</p>\n<h2>Limitations</h2>\n<p>It is important to remember that <code class=\"language-text\">Request.Pattern</code> is only available after the request has been matched to a route. For example:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token keyword\">func</span> <span class=\"token function\">main</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\tmux <span class=\"token operator\">:=</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">NewServeMux</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\tmux<span class=\"token punctuation\">.</span><span class=\"token function\">Handle</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/test/{id}\"</span><span class=\"token punctuation\">,</span> <span class=\"token function\">routeMiddleware</span><span class=\"token punctuation\">(</span><span class=\"token function\">handler</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n\tsrv <span class=\"token operator\">:=</span> http<span class=\"token punctuation\">.</span>Server<span class=\"token punctuation\">{</span>\n\t\tAddr<span class=\"token punctuation\">:</span>    <span class=\"token string\">\":8080\"</span><span class=\"token punctuation\">,</span>\n\t\tHandler<span class=\"token punctuation\">:</span> <span class=\"token function\">globalMiddleware</span><span class=\"token punctuation\">(</span>mux<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\t<span class=\"token punctuation\">}</span>\n\n\t<span class=\"token keyword\">if</span> err <span class=\"token operator\">:=</span> srv<span class=\"token punctuation\">.</span><span class=\"token function\">ListenAndServe</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span> <span class=\"token operator\">!</span>errors<span class=\"token punctuation\">.</span><span class=\"token function\">Is</span><span class=\"token punctuation\">(</span>err<span class=\"token punctuation\">,</span> http<span class=\"token punctuation\">.</span>ErrServerClosed<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Fatalf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"server error: %s\"</span><span class=\"token punctuation\">,</span> err<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">func</span> <span class=\"token function\">routeMiddleware</span><span class=\"token punctuation\">(</span>h http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"ROUTE pattern: %s\"</span><span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">)</span>\n\t\th<span class=\"token punctuation\">.</span><span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token keyword\">func</span> <span class=\"token function\">globalMiddleware</span><span class=\"token punctuation\">(</span>h http<span class=\"token punctuation\">.</span>Handler<span class=\"token punctuation\">)</span> http<span class=\"token punctuation\">.</span>Handler <span class=\"token punctuation\">{</span>\n\t<span class=\"token keyword\">return</span> http<span class=\"token punctuation\">.</span><span class=\"token function\">HandlerFunc</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">func</span><span class=\"token punctuation\">(</span>w http<span class=\"token punctuation\">.</span>ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>http<span class=\"token punctuation\">.</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"GLOBAL pattern: %s\"</span><span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">)</span>\n\t\th<span class=\"token punctuation\">.</span><span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">)</span>\n\t\tlog<span class=\"token punctuation\">.</span><span class=\"token function\">Printf</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"GLOBAL pattern after: %s\"</span><span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>If we send a request <code class=\"language-text\">GET http://localhost:8080/test/123</code>, in logs we will see:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">2024/11/10 13:20:37 GLOBAL pattern: \n2024/11/10 13:20:37 ROUTE pattern: /test/{id}\n2024/11/10 13:20:37 GLOBAL pattern after: /test/{id}</code></pre></div>\n<p>This is the expected behavior. The multiplexer implements the <code class=\"language-text\">http.Handler</code> interface, and inside the <code class=\"language-text\">ServeHTTP</code> method, it searches for the handler that matches the request. Starting from Go 1.23, it fills the <code class=\"language-text\">Pattern</code> property of the request with the matching route pattern:</p>\n<div class=\"gatsby-highlight\" data-language=\"go\"><pre class=\"language-go\"><code class=\"language-go\"><span class=\"token comment\">// net/http/server.go</span>\n<span class=\"token keyword\">func</span> <span class=\"token punctuation\">(</span>mux <span class=\"token operator\">*</span>ServeMux<span class=\"token punctuation\">)</span> <span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w ResponseWriter<span class=\"token punctuation\">,</span> r <span class=\"token operator\">*</span>Request<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\t<span class=\"token operator\">...</span>\n\t<span class=\"token keyword\">var</span> h Handler\n\t<span class=\"token keyword\">if</span> use121 <span class=\"token punctuation\">{</span>\n\t\th<span class=\"token punctuation\">,</span> <span class=\"token boolean\">_</span> <span class=\"token operator\">=</span> mux<span class=\"token punctuation\">.</span>mux121<span class=\"token punctuation\">.</span><span class=\"token function\">findHandler</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n\t\th<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>Pattern<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>pat<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">.</span>matches <span class=\"token operator\">=</span> mux<span class=\"token punctuation\">.</span><span class=\"token function\">findHandler</span><span class=\"token punctuation\">(</span>r<span class=\"token punctuation\">)</span>\n\t<span class=\"token punctuation\">}</span>\n\th<span class=\"token punctuation\">.</span><span class=\"token function\">ServeHTTP</span><span class=\"token punctuation\">(</span>w<span class=\"token punctuation\">,</span> r<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Thus, the <code class=\"language-text\">r.Pattern</code> is filled only after the <code class=\"language-text\">ServeHTTP</code> method of the multiplexer has been called, and the logs confirm this behavior.</p>\n<h2>Conclusion</h2>\n<p>In the last two Go releases, the <code class=\"language-text\">net/http</code> package has received significant improvements that reduced the gap with third-party packages, like <code class=\"language-text\">gorilla/mux</code>. Now, <code class=\"language-text\">net/http.ServeMux</code> supports HTTP methods and wildcards in routing patterns, bringing its functionality closer to what third-party packages previously provided.</p>\n<p>Additionally, the introduction of the <code class=\"language-text\">net/http.Request.Pattern</code> property in Go 1.23 has simplified middleware development by allowing us to access the route pattern without needing to re-match the request, improving performance and reducing code complexity.</p>\n<p>With each of these improvements, there are fewer reasons to choose third-party routers. While the choice between the standard package and third-party routers used to be more obvious, today I would recommend starting with the standard <code class=\"language-text\">net/http</code> package.</p>","fields":{"slug":"/go-123-the-new-pattern-property-in-http-request/","tags":["auto1","go","network_programming"]}}},{"node":{"id":"9720ef25-7d81-5307-9ea5-229d55ec766f","frontmatter":{"category":"Engineering","title":"Android JetPack Compose + Paging 3","date":"2024-03-29","summary":"A short trip around the AUTO1 Application Cockpit data model","thumbnail":null,"authorName":"Sergey Bakhtiarov","authorDescription":"Senior Android Developer at AUTO1 Group","authorAvatar":{"relativePath":"pages/android-compose-pagination/avatar_Sergey.jpeg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEBgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgD/2gAMAwEAAhADEAAAAessVEW4wzlnkZ2tJg3/xAAdEAACAQQDAAAAAAAAAAAAAAABAgMABCExECIz/9oACAEBAAEFAsAAq1EVIe0OSdtKzEXEgbfMHh//xAAXEQADAQAAAAAAAAAAAAAAAAAQERIh/9oACAEDAQE/AZxhj//EABURAQEAAAAAAAAAAAAAAAAAABEg/9oACAECAQE/AWP/xAAdEAACAgEFAAAAAAAAAAAAAAABEQAQAiAhImFx/9oACAEBAAY/AtzOJt9U3GCtGHk//8QAGxABAAIDAQEAAAAAAAAAAAAAAQARECExQVH/2gAIAQEAAT8hd4gIS0NTZPIjUV1DC40rgQLqvDjL0fpeUqrP/9oADAMBAAIAAwAAABD/APKz/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8QBR//xAAXEQEBAQEAAAAAAAAAAAAAAAABEBEh/9oACAECAQE/EOnIBP/EAB0QAQEBAAMAAwEAAAAAAAAAAAERACExQVFhcdH/2gAIAQEAAT8QaCJVXrPnop5ivjMtoBBeM7oBFG2x3Zjtir0D+518AlC+E9yQMgcPsxpw/mVwqOXf/9k=","width":50,"height":50,"src":"/static/27c2b15cdd840519c853a653e9b049f0/d2d31/avatar_Sergey.jpeg","srcSet":"/static/27c2b15cdd840519c853a653e9b049f0/d2d31/avatar_Sergey.jpeg 1x,\n/static/27c2b15cdd840519c853a653e9b049f0/0b804/avatar_Sergey.jpeg 1.5x,\n/static/27c2b15cdd840519c853a653e9b049f0/753c3/avatar_Sergey.jpeg 2x,\n/static/27c2b15cdd840519c853a653e9b049f0/31ca8/avatar_Sergey.jpeg 3x"}}},"headerImage":null},"html":"<h1>Android JetPack Compose + Paging 3</h1>\n<h2>About pagination</h2>\n<p>Paginated APIs, often encountered when dealing with servers and databases, break down large datasets into manageable chunks or pages. This methodology not only optimizes resource usage but also enhances the overall user experience by delivering content progressively. However, integrating and managing paginated data in a mobile app can be a challenge, from maintaining loading states to ensuring a seamless transition between pages and supporting content filtering.</p>\n<p>In this guide I will show you how to work with paginated APIs in Android with Jetpack Compose and Paging 3 libraries. </p>\n<p><img src=\"/static/paging_demo-a325402259bf9864dced311c9eafa7c8.gif\" alt=\"Paging UI\"></p>\n<h2>Basics, architecture overview</h2>\n<p><br></br>\n<img src=\"https://developer.android.com/static/topic/libraries/architecture/images/paging3-library-architecture.svg\" alt=\"Paging library overview\"></p>\n<p>The key components of the Paging 3 architecture include</p>\n<h3>PagingSource</h3>\n<ul>\n<li>The foundational element responsible for loading data in chunks.</li>\n<li>Developers implement a custom PagingSource, defining how to retrieve data from a particular source or use implementation provided by a library supporting Paging 3. (Room can generate a paging source for your data query)</li>\n</ul>\n<h3>Remote Mediator</h3>\n<ul>\n<li>An integral part of the Paging 3 architecture, RemoteMediator manages the coordination between remote data sources, typically backed by a network service, and the local database.</li>\n<li>Responsible for loading pages of data from the network and storing them in the local database, ensuring efficient and reliable pagination.</li>\n</ul>\n<h3>Paging Data</h3>\n<ul>\n<li>Represents the paginated data stream emitted by the PagingSource.</li>\n<li>A Flow of PagingData is observed in the UI layer, enabling dynamic updates as new data is loaded or existing data is invalidated.</li>\n</ul>\n<h3>Pager</h3>\n<ul>\n<li>Coordinates the interaction between the PagingSource and the UI layer.</li>\n<li>Configures the pagination parameters, such as page size, prefetch distance, and initial load size, providing fine-grained control over the loading behavior.</li>\n</ul>\n<h2>Data layer</h2>\n<p>We start with the data layer and define a Retrofit API for fetching movies from network service.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">interface</span> MoviesApi <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation builtin\">@GET</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/3/discover/movie?language=en-US&amp;sort_by=popularity.desc\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">discover</span><span class=\"token punctuation\">(</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"api_key\"</span><span class=\"token punctuation\">)</span> api_key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"page\"</span><span class=\"token punctuation\">)</span> page<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"with_genres\"</span><span class=\"token punctuation\">)</span> genres<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Response<span class=\"token operator\">&lt;</span>MoviesNetworkResponse<span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Next, we will leverage the Room library, which is compatible with Paging 3, to create a local paging source. </p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token annotation builtin\">@Dao</span>\n<span class=\"token keyword\">interface</span> MoviesDao <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT * FROM movies ORDER BY id ASC\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">fun</span> <span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> DataSource<span class=\"token punctuation\">.</span>Factory<span class=\"token operator\">&lt;</span>Int<span class=\"token punctuation\">,</span> MovieDbEntity<span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>It's important to highlight that we utilize <code class=\"language-text\">DataSource.Factory</code> as the return type instead of <code class=\"language-text\">PagingSource</code>. This choice is made specifically because <code class=\"language-text\">DataSource.Factory</code> offers the <code class=\"language-text\">map()</code> method, enabling us to map <code class=\"language-text\">MovieDbEntity</code> instances into domain layer models.</p>\n<p>Now we need to put together local and remote data sources in Remote mediator.</p>\n<ol>\n<li>Determine which page to load depending on the loadType and next page value. Next page number (<code class=\"language-text\">getRemoteKey().nextPage</code>) is stored in the database after each successful network request.</li>\n</ol>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">val</span> page <span class=\"token operator\">=</span> <span class=\"token keyword\">when</span> <span class=\"token punctuation\">(</span>loadType<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        LoadType<span class=\"token punctuation\">.</span>REFRESH <span class=\"token operator\">-></span> <span class=\"token number\">1</span>\n        LoadType<span class=\"token punctuation\">.</span>PREPEND <span class=\"token operator\">-></span> <span class=\"token keyword\">null</span>\n        LoadType<span class=\"token punctuation\">.</span>APPEND <span class=\"token operator\">-></span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getRemoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">?</span><span class=\"token punctuation\">.</span>nextPage\n      <span class=\"token punctuation\">}</span> <span class=\"token operator\">?:</span> <span class=\"token keyword\">return</span> <span class=\"token function\">Success</span><span class=\"token punctuation\">(</span>endOfPaginationReached <span class=\"token operator\">=</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>For bidirectional pagination support, provide the preceding page number for the PREPEND load type. The REFRESH load type is employed when content is refreshed, initiating loading from the initial page.</p>\n<ol start=\"2\">\n<li>Request data from network</li>\n</ol>\n<p>Call our network data source method to fetch a page of data from the server.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> moviesDataSource<span class=\"token punctuation\">.</span><span class=\"token function\">discover</span><span class=\"token punctuation\">(</span>page<span class=\"token punctuation\">)</span></code></pre></div>\n<ol start=\"3\">\n<li>Insert data into the database or refresh database if loadType is refresh</li>\n</ol>\n<p>The RemoteMediator code will appear as follows:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> <span class=\"token function\">MoviesRemoteMediator</span><span class=\"token punctuation\">(</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> repository<span class=\"token operator\">:</span> MoviesRepository<span class=\"token punctuation\">,</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> moviesDataSource<span class=\"token operator\">:</span> NetworkMoviesDataSource<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span> <span class=\"token operator\">:</span> RemoteMediator<span class=\"token operator\">&lt;</span>Int<span class=\"token punctuation\">,</span> Movie<span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">load</span><span class=\"token punctuation\">(</span>loadType<span class=\"token operator\">:</span> LoadType<span class=\"token punctuation\">,</span> state<span class=\"token operator\">:</span> PagingState<span class=\"token operator\">&lt;</span>Int<span class=\"token punctuation\">,</span> Movie<span class=\"token operator\">></span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MediatorResult <span class=\"token punctuation\">{</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n\n      <span class=\"token keyword\">val</span> page <span class=\"token operator\">=</span> <span class=\"token keyword\">when</span> <span class=\"token punctuation\">(</span>loadType<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        LoadType<span class=\"token punctuation\">.</span>REFRESH <span class=\"token operator\">-></span> <span class=\"token number\">1</span>\n        LoadType<span class=\"token punctuation\">.</span>PREPEND <span class=\"token operator\">-></span> <span class=\"token keyword\">null</span>\n        LoadType<span class=\"token punctuation\">.</span>APPEND <span class=\"token operator\">-></span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getRemoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">?</span><span class=\"token punctuation\">.</span>nextPage\n      <span class=\"token punctuation\">}</span> <span class=\"token operator\">?:</span> <span class=\"token keyword\">return</span> <span class=\"token function\">Success</span><span class=\"token punctuation\">(</span>endOfPaginationReached <span class=\"token operator\">=</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span>\n\n      <span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> moviesDataSource<span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span>page<span class=\"token punctuation\">)</span>\n\n      <span class=\"token keyword\">val</span> nextPage <span class=\"token operator\">=</span> <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">.</span><span class=\"token function\">isNotEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> page <span class=\"token operator\">+</span> <span class=\"token number\">1</span> <span class=\"token keyword\">else</span> <span class=\"token keyword\">null</span>\n\n      repository<span class=\"token punctuation\">.</span><span class=\"token function\">insertMovies</span><span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">,</span> nextPage<span class=\"token punctuation\">,</span> loadType <span class=\"token operator\">==</span> LoadType<span class=\"token punctuation\">.</span>REFRESH<span class=\"token punctuation\">)</span>\n\n      <span class=\"token function\">Success</span><span class=\"token punctuation\">(</span>endOfPaginationReached <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token operator\">:</span> Exception<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      MediatorResult<span class=\"token punctuation\">.</span><span class=\"token function\">Error</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>RemoteMediator uses the repository to store movies list and next page number in the database.\nRepository also takes care of clearing the database table when data is refreshed:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> <span class=\"token function\">MoviesRepositoryImpl</span><span class=\"token punctuation\">(</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> database<span class=\"token operator\">:</span> MoviesDatabase\n<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MoviesRepository <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">map</span> <span class=\"token punctuation\">{</span> it<span class=\"token punctuation\">.</span><span class=\"token function\">toDomain</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">insertMovies</span><span class=\"token punctuation\">(</span>movies<span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>Movie<span class=\"token operator\">></span><span class=\"token punctuation\">,</span> nextPage<span class=\"token operator\">:</span> Int<span class=\"token operator\">?</span><span class=\"token punctuation\">,</span> clear<span class=\"token operator\">:</span> Boolean<span class=\"token punctuation\">)</span><span class=\"token punctuation\">{</span>\n\n    database<span class=\"token punctuation\">.</span><span class=\"token function\">withTransaction</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>clear<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">clear</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n      <span class=\"token punctuation\">}</span>\n\n      database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">putMovies</span><span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">.</span><span class=\"token function\">map</span> <span class=\"token punctuation\">{</span> it<span class=\"token punctuation\">.</span><span class=\"token function\">toDbEntity</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\">if</span> <span class=\"token punctuation\">(</span>nextPage <span class=\"token operator\">!=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        database<span class=\"token punctuation\">.</span><span class=\"token function\">remoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">insertKey</span><span class=\"token punctuation\">(</span><span class=\"token function\">RemoteKey</span><span class=\"token punctuation\">(</span>MoviesDatabase<span class=\"token punctuation\">.</span>MOVIES_REMOTE_KEY<span class=\"token punctuation\">,</span> nextPage<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>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h2>UI layer</h2>\n<p>The Pager object serves as a bridge between remote and local data sources. It is managing the page loading, prefetching, and handling the transitions between various data sources to provide a seamless and responsive flow of paginated content within your application. You can configure the page loading behavior with the following parameters:</p>\n<ul>\n<li><strong>pageSize</strong> parameter defines the number of items loaded in each page of data.</li>\n<li><strong>initialLoadSize</strong> is the number of items loaded initially when the paging library is first initialized.</li>\n<li><strong>prefetchDistance</strong> parameter determines the distance from the edge of the loaded content at which the next page should start loading.</li>\n</ul>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> <span class=\"token function\">MoviesScreenViewModel</span><span class=\"token punctuation\">(</span>\n  moviesSource<span class=\"token operator\">:</span> MoviesRemoteMediator<span class=\"token punctuation\">,</span>\n  repository<span class=\"token operator\">:</span> MoviesRepository<span class=\"token punctuation\">,</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> movieDetails<span class=\"token operator\">:</span> MovieDetailsApi<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span> <span class=\"token operator\">:</span> <span class=\"token function\">ViewModel</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n   <span class=\"token keyword\">val</span> moviesFlow <span class=\"token operator\">=</span> <span class=\"token function\">Pager</span><span class=\"token punctuation\">(</span>\n      config <span class=\"token operator\">=</span> <span class=\"token function\">PagingConfig</span><span class=\"token punctuation\">(</span>\n        pageSize <span class=\"token operator\">=</span> PAGE_SIZE<span class=\"token punctuation\">,</span>\n        prefetchDistance <span class=\"token operator\">=</span> PREFETCH_DISTANCE<span class=\"token punctuation\">,</span>\n        initialLoadSize <span class=\"token operator\">=</span> INITIAL_LOAD<span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      remoteMediator <span class=\"token operator\">=</span> moviesSource<span class=\"token punctuation\">,</span>\n      pagingSourceFactory <span class=\"token operator\">=</span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">asPagingSourceFactory</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>flow<span class=\"token punctuation\">.</span><span class=\"token function\">cachedIn</span><span class=\"token punctuation\">(</span>viewModelScope<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3>Consuming paging data in Jetpack Compose UI</h3>\n<p>Now, let's explore how to consume the paginated data in your Compose UI.</p>\n<p>Use the <code class=\"language-text\">collectAsLazyPagingItems</code> extension function to collect the <code class=\"language-text\">PagingData</code> and observe changes.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> viewModel<span class=\"token punctuation\">.</span>moviesFlow<span class=\"token punctuation\">.</span><span class=\"token function\">collectAsLazyPagingItems</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Use the <code class=\"language-text\">LazyColumn</code> or <code class=\"language-text\">LazyVerticalGrid</code> to efficiently display paginated data in your Compose UI.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token function\">LazyVerticalGrid</span><span class=\"token punctuation\">(</span>\n      modifier <span class=\"token operator\">=</span> Modifier\n        <span class=\"token punctuation\">.</span><span class=\"token function\">fillMaxSize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">padding</span><span class=\"token punctuation\">(</span><span class=\"token number\">16</span><span class=\"token punctuation\">.</span>dp<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      columns <span class=\"token operator\">=</span> GridCells<span class=\"token punctuation\">.</span><span class=\"token function\">Fixed</span><span class=\"token punctuation\">(</span><span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      horizontalArrangement <span class=\"token operator\">=</span> Arrangement<span class=\"token punctuation\">.</span><span class=\"token function\">spacedBy</span><span class=\"token punctuation\">(</span><span class=\"token number\">16</span><span class=\"token punctuation\">.</span>dp<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      verticalArrangement <span class=\"token operator\">=</span> Arrangement<span class=\"token punctuation\">.</span><span class=\"token function\">spacedBy</span><span class=\"token punctuation\">(</span><span class=\"token number\">16</span><span class=\"token punctuation\">.</span>dp<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\n      <span class=\"token function\">items</span><span class=\"token punctuation\">(</span>\n        count <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">.</span>itemCount<span class=\"token punctuation\">,</span>\n        key <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">itemKey</span> <span class=\"token punctuation\">{</span> it<span class=\"token punctuation\">.</span>id <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> index <span class=\"token operator\">-></span>\n\n        movies<span class=\"token punctuation\">[</span>index<span class=\"token punctuation\">]</span><span class=\"token operator\">?</span><span class=\"token punctuation\">.</span><span class=\"token function\">let</span> <span class=\"token punctuation\">{</span> movie <span class=\"token operator\">-></span>\n          <span class=\"token function\">MovieCardSmall</span><span class=\"token punctuation\">(</span>\n            movie <span class=\"token operator\">=</span> movie<span class=\"token punctuation\">,</span>\n            onClick <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> <span class=\"token function\">onClick</span><span class=\"token punctuation\">(</span>movie<span class=\"token punctuation\">.</span>id<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>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<hr>\n<blockquote>\n<p><em>Note the usage of the key property for items. The key property uniquely identifies each item, allowing Compose to efficiently update and recycle composables as data changes.</em></p>\n</blockquote>\n<hr>\n<h3>Paging state handling</h3>\n<p>LazyPagingItems offers insights into the ongoing data loading process, allowing you to effectively manage errors during pagination. By examining the loadState.append property, you can determine the loading state when appending new items and present a user-friendly indication of the current status.</p>\n<p>For instance, the following code snippet demonstrates how to react to different loading states during item appending:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">when</span> <span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">.</span>loadState<span class=\"token punctuation\">.</span>append<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Loading <span class=\"token operator\">-></span> <span class=\"token punctuation\">{</span> <span class=\"token function\">ProgressFooter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Error <span class=\"token operator\">-></span> <span class=\"token punctuation\">{</span> <span class=\"token function\">ErrorFooter</span><span class=\"token punctuation\">(</span>onRetryClick <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">retry</span><span class=\"token punctuation\">(</span><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>Similarly, you can assess the loading status when the list data is refreshed by checking the loadState.refresh property. Extension functions isError() and isLoading() provide convenient checks for error and loading states, respectively.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">fun</span> LazyPagingItems<span class=\"token operator\">&lt;</span>Movie<span class=\"token operator\">></span><span class=\"token punctuation\">.</span><span class=\"token function\">isError</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Boolean <span class=\"token operator\">=</span> \n    loadState<span class=\"token punctuation\">.</span>refresh <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Error <span class=\"token operator\">&amp;&amp;</span> itemSnapshotList<span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">fun</span> LazyPagingItems<span class=\"token operator\">&lt;</span>Movie<span class=\"token operator\">></span><span class=\"token punctuation\">.</span><span class=\"token function\">isLoading</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Boolean <span class=\"token operator\">=</span> \n    loadState<span class=\"token punctuation\">.</span>refresh <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Loading <span class=\"token operator\">&amp;&amp;</span> itemSnapshotList<span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Incorporate these checks into your UI logic to display appropriate screens based on the loading or error states. The following code snippet demonstrates how to handle loading and error states along with providing a retry option:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">when</span> <span class=\"token punctuation\">{</span>\n   movies<span class=\"token punctuation\">.</span><span class=\"token function\">isLoading</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token function\">LoadingScreen</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n   movies<span class=\"token punctuation\">.</span><span class=\"token function\">isError</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> ErrorScreen <span class=\"token punctuation\">{</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">retry</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n   <span class=\"token keyword\">else</span> <span class=\"token operator\">-></span> <span class=\"token function\">MoviesGrid</span><span class=\"token punctuation\">(</span>movies <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">,</span> onClick <span class=\"token operator\">=</span> viewModel<span class=\"token operator\">::</span>onMovieClick<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Additionally, <code class=\"language-text\">LazyPagingItems</code> offers a convenient <code class=\"language-text\">retry()</code> method, allowing you to retry the last failed data loading attempt with ease.</p>\n<h3>Adding filters</h3>\n<p>Now lets add filters to our movie list to see paginated list of movies filtered by genre.</p>\n<p><img src=\"/static/paging_demo_filters-ff80a7606fb30961e2bda56bd6ed9985.gif\" alt=\"Filters UI\"></p>\n<p>We start with requesting genres from the server and saving them in our database. We will keep genre data and selection in the database so that we can observe it from our domain and UI layers.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">interface</span> MoviesApi <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation builtin\">@GET</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/3/genre/movie/list?language=en-US\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">genres</span><span class=\"token punctuation\">(</span><span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"api_key\"</span><span class=\"token punctuation\">)</span> api_key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Response<span class=\"token operator\">&lt;</span>GenresNetworkResponse<span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token annotation builtin\">@Dao</span>\n<span class=\"token keyword\">interface</span> GenresDao <span class=\"token punctuation\">{</span>\n\n  <span class=\"token annotation builtin\">@Insert</span><span class=\"token punctuation\">(</span>onConflict <span class=\"token operator\">=</span> OnConflictStrategy<span class=\"token punctuation\">.</span>REPLACE<span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">putGenres</span><span class=\"token punctuation\">(</span>genres<span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>GenreDbEntity<span class=\"token operator\">></span><span class=\"token punctuation\">)</span>\n\n  <span class=\"token annotation builtin\">@Upsert</span><span class=\"token punctuation\">(</span>entity <span class=\"token operator\">=</span> GenreDbEntity<span class=\"token operator\">::</span><span class=\"token keyword\">class</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">updateGenres</span><span class=\"token punctuation\">(</span>genres<span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>GenreDbUpdateEntity<span class=\"token operator\">></span><span class=\"token punctuation\">)</span>\n\n  <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT * FROM genres\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">getGenres</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>GenreDbEntity<span class=\"token operator\">></span>\n\n  <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT * FROM genres\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">fun</span> <span class=\"token function\">observeGenres</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Flow<span class=\"token operator\">&lt;</span>List<span class=\"token operator\">&lt;</span>GenreDbEntity<span class=\"token operator\">></span><span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Now we can add <code class=\"language-text\">with_genres</code> parameter to our network request:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\">  <span class=\"token annotation builtin\">@GET</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/3/discover/movie?language=en-US&amp;sort_by=popularity.desc\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">discover</span><span class=\"token punctuation\">(</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"page\"</span><span class=\"token punctuation\">)</span> page<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"with_genres\"</span><span class=\"token punctuation\">)</span> genres<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"api_key\"</span><span class=\"token punctuation\">)</span> api_key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Response<span class=\"token operator\">&lt;</span>MoviesNetworkResponse<span class=\"token operator\">></span></code></pre></div>\n<p>Remote mediator will get selected genres from the repository</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\">  <span class=\"token operator\">..</span><span class=\"token punctuation\">.</span>\n  <span class=\"token keyword\">val</span> genres <span class=\"token operator\">=</span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getGenreSelection</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> moviesDataSource<span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span>page<span class=\"token punctuation\">,</span> genres<span class=\"token punctuation\">)</span>\n  <span class=\"token operator\">..</span><span class=\"token punctuation\">.</span></code></pre></div>\n<p>Every time user selects a new filter we stave it in the database and restart pagination by clearing movies in the database and resetting the page to 1.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> MoviesScreenViewModel <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">fun</span> <span class=\"token function\">onGenreClick</span><span class=\"token punctuation\">(</span>genreItem<span class=\"token operator\">:</span> GenreItem<span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> viewModelScope<span class=\"token punctuation\">.</span><span class=\"token function\">launch</span> <span class=\"token punctuation\">{</span>\n    genresProvider<span class=\"token punctuation\">.</span><span class=\"token function\">setSelection</span><span class=\"token punctuation\">(</span>genreItem<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">,</span> <span class=\"token operator\">!</span>genreItem<span class=\"token punctuation\">.</span>isSelected<span class=\"token punctuation\">)</span>\n    repository<span class=\"token punctuation\">.</span><span class=\"token function\">clearMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n  \n<span class=\"token punctuation\">}</span></code></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> MoviesRepositoryImpl <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">clearMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token function\">withContext</span><span class=\"token punctuation\">(</span>Dispatchers<span class=\"token punctuation\">.</span>IO<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    database<span class=\"token punctuation\">.</span><span class=\"token function\">withTransaction</span> <span class=\"token punctuation\">{</span>\n      database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">clear</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n      database<span class=\"token punctuation\">.</span><span class=\"token function\">remoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">insertKey</span><span class=\"token punctuation\">(</span><span class=\"token function\">RemoteKey</span><span class=\"token punctuation\">(</span>MoviesDatabase<span class=\"token punctuation\">.</span>MOVIES_REMOTE_KEY<span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3>Links</h3>\n<p><br>Source code: <a href=\"https://github.com/auto1-oss/android-compose-paging\">https://github.com/auto1-oss/android-compose-paging</a></br>\n<br>Official documentation: <a href=\"https://developer.android.com/topic/libraries/architecture/paging/v3-overview\">https://developer.android.com/topic/libraries/architecture/paging/v3-overview</a></br></p>","fields":{"slug":"/android-compose-pagination/","tags":["auto1","mobile","Android"]}}},{"node":{"id":"2cda3518-ba96-5257-97f4-6f0ec1f69eb1","frontmatter":{"category":"Engineering","title":"Bug Police Officer","date":"2023-11-21","summary":"What I present today is how to use police techniques and their mindset to detect and solve bugs in software, so that you can be the best software developer that society can provide.","thumbnail":{"relativePath":"pages/bug-police-officer/bug-police-officer.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAC20lEQVQ4y32S208TURCHF6rvvPpI/Ad8MzFIIlESaAuoAZVehEppd7fQFqjhIgiBQhEUaoFy6wUCghRrCFKQi4orhgQ0XOIFA8FoqOKTaHyg7e54zpYWUOsks2fOmd98Z85mCOIPqyNtUQf33Xp7XOZl+7oEua3AfvpgzkTZo4hIVq9oC8PQGluh7M4hpdYh0fl2aFFfh1a1AcQX2oGUWV0ol2si7bH/auAQsFptE+C4VesUZUnscPxMMxyLa2FXikXsKnIc4zOca9M5hVhbs1cT0fKzuniBkbTnSqU2iBeaA/KMZg47jmVSB9RSDjXW6BXdgv8+mf8vpDMar02qzlMG+gGkKz1cunI06Lkergid3VF1xR3URrRG2k48HZziRf31wxVrzCI01I4HZNQYh73RNBF4P7sAvUZXGdZMOMajcU1E2G3aRjDuJ4I9oAV+fQPY2fB9fbsE2OHHhg9+bkOP0dWENdP9k4JbtIP4C2qinfzq0LQTN8i+I8HTE0eZoak18H8B3/dN1rezyeKYGZx8h5L8pVjbr7EcYoTpnryG8AVLhuqYLk2Dtir/nnfrzQqA7xMLvs/s1uoylObd91ZRvboPRZUxIX2oNtxpB+osZDV0j44mR7wfKy+CRVsHGsM0++oxAwsTL8BgGGUTVAxc0r0EinJ7jXSvLlQXZmBqCTUQHBW6h1YWMhBPLkKfvsi/XprAJimfQbLCw4kVY9xZ5Qxco0ZZCenxn6MYyCmcAwSlcS1m8B3yHzXw066nh8dTtfOQSD3fldETsF12Emq1Fi6BmueSNLNcJjXDjZVL4GEJCSJybjcFaQtQDd8ZYuwDCQgOtWpgMl35CMRZrkCi3MMN5athuVgIomwPpGS7QCx3Q7WmGKrIm1ySfCSAZhNwTfCtQOwDY8v5DrVXO9wZGZ2Qkmb2J6fe5SipGV6XXIE8tKI9CNPMIEy1otjKiZAGa3ENz0MMzPoNzyOqI3a+TN4AAAAASUVORK5CYII=","width":460,"height":325,"src":"/static/19a57f273a6f7441d951955ce11fae27/b3029/bug-police-officer.png","srcSet":"/static/19a57f273a6f7441d951955ce11fae27/b3029/bug-police-officer.png 1x"}}},"authorName":"Bruno Morais","authorDescription":"Bruno is a Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/bug-police-officer/avatar.jpeg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQDAQX/xAAXAQEBAQEAAAAAAAAAAAAAAAABAwIE/9oADAMBAAIQAxAAAAGmbSadPSZMPIBLBy1//8QAHBAAAwABBQAAAAAAAAAAAAAAAQIDIQAQERIj/9oACAEBAAEFAqOVEql2047LLDc7A+i5H//EABcRAQEBAQAAAAAAAAAAAAAAAAEAEBL/2gAIAQMBAT8BC5jP/8QAGREAAwADAAAAAAAAAAAAAAAAAAECEBET/9oACAECAQE/AW9HSSsf/8QAGhAAAQUBAAAAAAAAAAAAAAAAAQACEBEhQf/aAAgBAQAGPwLBZRa4VFYt5Jj/xAAcEAADAAIDAQAAAAAAAAAAAAAAAREhURAxYXH/2gAIAQEAAT8ho6G+i0ElVBP0c6AzfMaH3OHLLoTIz//aAAwDAQACAAMAAAAQcC98/8QAGREAAwADAAAAAAAAAAAAAAAAABExEFGB/9oACAEDAQE/EGQbRPcf/8QAGxEAAgIDAQAAAAAAAAAAAAAAAAERMVFhscH/2gAIAQIBAT8QuM2dElucejs//8QAGxABAQEBAAMBAAAAAAAAAAAAAREAITFBYXH/2gAIAQEAAT8QEiRCoPrgpVComQJxnQidLfOE1gCkvzQ9j4TPv80LcHh6dw+UV3//2Q==","width":50,"height":50,"src":"/static/8d7223890dda036e0121799406a35506/d2d31/avatar.jpeg","srcSet":"/static/8d7223890dda036e0121799406a35506/d2d31/avatar.jpeg 1x,\n/static/8d7223890dda036e0121799406a35506/0b804/avatar.jpeg 1.5x,\n/static/8d7223890dda036e0121799406a35506/753c3/avatar.jpeg 2x,\n/static/8d7223890dda036e0121799406a35506/31ca8/avatar.jpeg 3x"}}},"headerImage":{"relativePath":"pages/bug-police-officer/bug-police-officer.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAAC20lEQVQ4y32S208TURCHF6rvvPpI/Ad8MzFIIlESaAuoAZVehEppd7fQFqjhIgiBQhEUaoFy6wUCghRrCFKQi4orhgQ0XOIFA8FoqOKTaHyg7e54zpYWUOsks2fOmd98Z85mCOIPqyNtUQf33Xp7XOZl+7oEua3AfvpgzkTZo4hIVq9oC8PQGluh7M4hpdYh0fl2aFFfh1a1AcQX2oGUWV0ol2si7bH/auAQsFptE+C4VesUZUnscPxMMxyLa2FXikXsKnIc4zOca9M5hVhbs1cT0fKzuniBkbTnSqU2iBeaA/KMZg47jmVSB9RSDjXW6BXdgv8+mf8vpDMar02qzlMG+gGkKz1cunI06Lkergid3VF1xR3URrRG2k48HZziRf31wxVrzCI01I4HZNQYh73RNBF4P7sAvUZXGdZMOMajcU1E2G3aRjDuJ4I9oAV+fQPY2fB9fbsE2OHHhg9+bkOP0dWENdP9k4JbtIP4C2qinfzq0LQTN8i+I8HTE0eZoak18H8B3/dN1rezyeKYGZx8h5L8pVjbr7EcYoTpnryG8AVLhuqYLk2Dtir/nnfrzQqA7xMLvs/s1uoylObd91ZRvboPRZUxIX2oNtxpB+osZDV0j44mR7wfKy+CRVsHGsM0++oxAwsTL8BgGGUTVAxc0r0EinJ7jXSvLlQXZmBqCTUQHBW6h1YWMhBPLkKfvsi/XprAJimfQbLCw4kVY9xZ5Qxco0ZZCenxn6MYyCmcAwSlcS1m8B3yHzXw066nh8dTtfOQSD3fldETsF12Emq1Fi6BmueSNLNcJjXDjZVL4GEJCSJybjcFaQtQDd8ZYuwDCQgOtWpgMl35CMRZrkCi3MMN5athuVgIomwPpGS7QCx3Q7WmGKrIm1ySfCSAZhNwTfCtQOwDY8v5DrVXO9wZGZ2Qkmb2J6fe5SipGV6XXIE8tKI9CNPMIEy1otjKiZAGa3ENz0MMzPoNzyOqI3a+TN4AAAAASUVORK5CYII=","width":600,"height":424,"src":"/static/19a57f273a6f7441d951955ce11fae27/b7ca8/bug-police-officer.png","srcSet":"/static/19a57f273a6f7441d951955ce11fae27/b7ca8/bug-police-officer.png 1x"}}}},"html":"<h3>Table of contents:</h3>\n<ol>\n<li>\n<p>Sense of Security</p>\n<ol>\n<li>Why does feeling safe matter?</li>\n<li>Strategic actions</li>\n<li>A test as a Cop</li>\n</ol>\n</li>\n<li>\n<p>Treat the injured first</p>\n<ol>\n<li>Prioritizing what matters</li>\n<li>The appropriate Mental Model</li>\n<li>Ask before trying to find out</li>\n</ol>\n</li>\n<li>\n<p>Trust your tools</p>\n<ol>\n<li>When less is more</li>\n<li>Efficient distribution of efforts</li>\n<li>Confidence that avoids shame</li>\n</ol>\n</li>\n<li>\n<p>Communication via Radio</p>\n<ol>\n<li>Q-code</li>\n<li>The Language and the Domain</li>\n</ol>\n</li>\n<li>Bug is in jail</li>\n</ol>\n<p><strong>What I present today is how to use police techniques and their mindset to detect and solve bugs in software, so that you can be the best software developer that society can provide.</strong></p>\n<p>For some years, I worked as a police officer in Brazil and I can say that it is a challenging career. The first step was to pass a national test, where knowledge of various laws was required. Criminal laws, traffic laws, and even the laws of physics.</p>\n<p>The second step was to pass a battery of medical exams. Almost perfect health was necessary to be able to practice the profession, in addition to having most of the parts of the human body.</p>\n<p>The third step was the psychological examination. It was necessary to have a clear mind and not have impulses different from those expected from a person who must protect other people.</p>\n<p>As a fourth part, there was also the fitness test, a test in which career candidates had to prove they had sufficient strength and speed to carry out daily police tasks. Running, pull-ups, sit-ups and long jump are examples of what was required.</p>\n<p>The last stage was the Police Academy, where for three months those from now on known as students would learn and prove the learning of several new skills: first aid, use of firearms (pistols, carbines, submachine guns, etc.), non-lethal weapons, martial arts, public policy, and many other disciplines.</p>\n<p>All this so that society receives the best police officer that its people can provide. So, what about using some of the following principles and techniques to become the best software developer that society can provide?</p>\n<h2>Sense of Security</h2>\n<h3>Why does feeling safe matter?</h3>\n<p>Without needing to be a security expert, you already know that it is impossible to have a completely safe city. Even if we reached the point of having perfect police officers, which in itself is impossible, we would not have a police officer for every citizen. In general, the recommendation is 300 police officers for every 100 thousand inhabitants.</p>\n<p>Aware of this limitation, that it is not possible to guarantee complete security for all citizens, police agencies work with the concept of a sense of security. Through strategic actions to combat crime, citizens' feeling that they are safe is increased. This mental condition gives them peace of mind when working, studying, and carrying out their daily tasks.</p>\n<h3>Strategic actions</h3>\n<p>This sense of security also affects criminals. The common visibility of police officers carrying out their duties in strategic places, as well as effective actions to combat crime, make criminals stay away from that place.</p>\n<p>Notice that ending 100% of crime is impossible, what we do well is limit its actions. From here we can extract our first lesson when dealing with software: perfect code, totally bug-free, is impossible.</p>\n<p>Despite this conclusion, the fact that it is impossible does not mean that we should simply ignore the problem. On the contrary, we can, like the police, create strategic actions to combat Bugs. To the point that Bugs themselves will run away from our code.</p>\n<h3>A test as a Cop</h3>\n<p>Test Driven Development - TDD is a strategic action that provides a feeling of security in your code. Before the crime occurs, you position police officers in a specific region. Before the Bug occurs, you position the test in a specific place in your code.</p>\n<p>Robert C. Martin, in his book \"Clean Code: A HandBook of Agile Software Craftsmanship\", teaches us that writing unit tests before writing production code is just the tip of the iceberg in defining TDD. There must be a continuous cycle of improvement, in which there is always a test covering the code you are developing and this security that the code is protected allows you to play with it, and work with it. After all, whatever problem may happen, there is a police-test there to protect you.</p>\n<h2>Treat the injured first</h2>\n<h3>Prioritizing what matters</h3>\n<p>One of the functions of Highway Police Officers is to provide first aid in the event of traffic accidents. There are often serious injuries that must be treated as urgently as possible, as even 1 minute can make the difference between life and death.</p>\n<p>The first step when arriving at a traffic accident site is to mark the location appropriately to prevent other accidents. We must use lighting devices and other appliances to make it clear to other vehicles that there is a danger there.</p>\n<p>The police officer must then check the condition of all the injured to prioritize them according to the severity of each situation.</p>\n<p>Once the treatment of each injured person has been completed and, if necessary, they have been taken to the hospital, it is time to survey the accident site. At this stage, the police officer begins to collect all the information relating to the accident to have sufficient information so that a decision can be made on the cause of the accident and, when applicable, any culprits.</p>\n<h3>The appropriate Mental Model</h3>\n<p>This is the mental model that you and I must adopt when we are faced with a Bug. Although several procedures have been adopted to prevent it from occurring, it is a fatality that can happen. It's like when the highway was well designed, the vehicles were up to date with maintenance, but even so, due to carelessness or misunderstanding on the part of the driver, an accident happens.</p>\n<p>As in the case of traffic accidents, the first step to take is to secure the environment so that the Bug does not cause other problems. If possible, we can revert the application version to one in which the Bug did not appear. We can use a feature flag to temporarily disable a feature. Regardless of the method used to stop the bleeding, the important thing is that we must focus on treating the injured first rather than looking for culprits.</p>\n<p>We have to agree with David Thomas and Andrew Hunt, in their work \"The Pragmatic Programmer: your journey to mastery\" that facing Bugs is a sensitive subject for most developers. Instead of treating the issue as a problem to be solved, some approach it with denial, pointing fingers, lame excuses, or simply apathy. It's exactly the opposite of what is expected!</p>\n<p>Instead of looking for the culprit in a traffic accident, we must first assist the victims and stop the bleeding. In software, instead of finding out who inserted the Bug, we must solve it.</p>\n<h3>Ask before trying to find out</h3>\n<p>When treating an injured person in an emergency, one approach is to use the Glasgow Scale to determine the patient's consciousness. On this scale, one of the components is the verbal response.</p>\n<p>The injured person is asked simple questions, and by analyzing the responses, we can assess the level of consciousness and consequently the level of urgency of their care.</p>\n<p>You and I must carefully read every error message that is displayed during the Bug, it may contain vital information so that we can resolve the problem.</p>\n<p>You may be thinking, but what if there is no error message? If we are just faced with an incorrect result? Well, this also happens in traffic accidents, it is not uncommon for there to be an unconscious victim who cannot answer questions.</p>\n<p>When we discussed the sense of security, we talked about the importance of tests in our application. Go and build a test that can reproduce that Bug, so you can extract the information you need to solve it.</p>\n<p>Think of these tests like the motor tests that highway patrol officers perform on unconscious victims to check their level of consciousness: do the eyes open in response to pain? Is there a pupillary response to light stimulation? Mentally translate this into software language: Does the Bug repeat itself when I enter this and that parameter? And if I insert this new argument, does it happen too?</p>\n<p>To decide on a Bug, data is needed. If you don't receive this data, you must extract it.</p>\n<h2>Trust your tools</h2>\n<h3>When less is more</h3>\n<p>Police work is very complex and your day-to-day job involves the fight for life and protection of the most important assets for society. There would be no point in having a modern and efficient selection and training process for the development of police officers if they were not given the appropriate tools for their work.</p>\n<p>And when I say appropriate, I don't mean they are minimally capable of performing the task, as the result delivered is directly related to the quality and safety of the tools. For an excellent result, not only the police officers but their tools must also be excellent.</p>\n<p>In the police agency I worked for, one of the work tools used was an Austrian pistol known worldwide for its safety and efficiency. The Glock 17 pistol was developed to meet the strict criteria of the Austrian army who wanted to replace the model used during the Second World War.</p>\n<p>In addition to requirements on durability, efficiency, and reliability, safety against accidental firing was one of the pivots of the new model.</p>\n<p>But how did a knife factory manage to deliver the pistol model that would prove to be the safest? Well, it can be said that, in addition to intense testing, the secret to reliability was simplicity and maintainability. The person responsible for the project designed the equipment with as few parts as possible, minimizing complexity. Today the Glock pistol is made with an average of 35 parts, which is a much smaller quantity than other pistols on the market.</p>\n<p>With a simpler design, so-called first-level maintenance is much easier. Equipment users can easily perform maintenance procedures, making the equipment's efficiency last for decades.</p>\n<p>Of course, you must already remember the principle with the acronym KISS, which stands for Keep it Simple (omitting the last word because I don't want to offend the reader). The secret of that renowned pistol model was not in the used materials, but in being simple. The secret of maintaining your software in good shape is to keep it simple, having a specific component for each task. When a problem arises, you will know exactly which component to look for, as each component has a responsibility, this is the principle of Single Responsibility.</p>\n<p>Because of all this, when you are at a shooting range practicing your aim, the police officer knows that if he is not hitting the target, most likely, the error is in his procedure and not in the equipment.</p>\n<p>Allan Kelly, in one of his contributions to the Book \"97 Things Every Programmer Should Know\", corroborates this idea that we should trust our tools when dealing with Bugs. Considering our tools are widely used, mature, and in use by multiple companies, we have little reason to doubt their quality.</p>\n<p>Of course, a prototype, version 0.1, is much more fragile than a library that has been on the market for several years. We are also not saying that it is impossible to have bugs in compilers.</p>\n<p>But considering how rare compiler bugs are, what we are saying is that in the search for a Bug solution, we should trust our tools and look at our piece of code first.</p>\n<h3>Efficient distribution of efforts</h3>\n<p>Imagine it as an efficient distribution of efforts. The library or compiler code has already been reviewed and tested for many years by countless people. As for that little piece of code of yours, only God knows who besides you has looked at it on your team.</p>\n<p>On one of those days, I managed to solve an unpleasant bug that occurred in one of our services. It was a non-deterministic error in our test suite, that is, the Bug only pushed out its head when no one was looking.</p>\n<p>This type of non-deterministic error generally involves three types of origin: multithread, time sensitivity, and dissynchrony between construction and cleaning of test data.</p>\n<p>Analyzing the body of the failed test, after several hours, of course, I began to strongly distrust the way we created timestamps with a native Java library. After all, the test seemed perfect to me, if there was an error it must have been somewhere else.</p>\n<h3>Confidence that avoids shame</h3>\n<p>But, aware of this lesson that we should trust our tools, especially since the component in question had been in Java for 9 years, I ignored this distrust. This saved me a lot of time and embarrassment, as I'm going to share that the error was elsewhere.</p>\n<p>Our test suite ran all tests in a single thread, so it wouldn't be a concurrency issue. But it also ran each test in a deterministic but unpredictable order. Therefore, the source of the intermittent problem must be in some specific order in which the tests were performed.</p>\n<p>After several hours of analyzing the logs, I was able to identify two tests that failed when run in sequence. It turned out that, although our tests performed a test data cleaning routine after each execution, a simple typo had silently overwritten the cleaning method in one of the tests, which generated data inconsistency if such tests were executed in a specific order.</p>\n<h2>Communication via Radio</h2>\n<h3>Q-code</h3>\n<p>I remember that one of the first systems I developed when I worked in the police was one to replace the sending of statistics through radio communication. Many years ago, each police station was supposed to send daily work statistics via radio. Data such as number of people and vehicles inspected, car accidents, etc.</p>\n<p>This took a lot of time as the audio quality of radio communications was not always ideal. The interlocutors spent a lot of time transmitting and confirming each piece of information. All this using Q-Code</p>\n<p>The Q-Code is a standardized collection of three-letter codes used in radio communication. The use of Q-code even in voice transmissions makes communication safer and more efficient. Each set of three letters, always starting with the letter Q, has a specific meaning that is known to the interlocutors.</p>\n<p>I could see that two of those codes were widely used among police officers in radio communication: QAP and QSL.</p>\n<p>QAP was used when you wanted to transmit a message. Let's say someone wanted to talk to me, so he said on the radio: \"Bruno, QAP?\". This could be translated as \"Bruno, are you ready to hear a message?\"</p>\n<p>My answer could be QAP or for example QRM. If I responded with QAP, I was signaling that yes, I was ready to receive the message. If you responded with QRM, I would be informing you that at that moment the communication is being interfered with.</p>\n<p>During communication, the second most used Q-Code appears, QSL. At the end of each message, the sender ends it with the QSL. This means that he is asking the receiver to confirm whether they received the message. It would be something like: \"There is a suspicious vehicle heading towards your team, QSL?\" If I had understood the entire message, I should respond with a simple \"QSL\". In the absence of a response, the sender repeats the message until receiving confirmation from the receiver.</p>\n<p>Each message is concluded with a QSL, and its reception is confirmed with a QSL. Does this remind you of any transmission control protocol out here, used on the internet?</p>\n<p>The main thing here is that a large amount of knowledge is condensed into three letters, in a language that provides interlocutors with efficiency and security. Eric Evans, the author of \"Domain-Driven Design: Tackling Complexity in the Heart of Software\", would agree with me that there is a principle here that should also be adopted in software development.</p>\n<h3>The Language and the Domain</h3>\n<p>A language specific to the domain, which considers possible interference from the environment, a ubiquitous language that is known by all interlocutors, can be the difference between life and death, between success and damnation of a project.</p>\n<p>When a project does not have its own language for that domain, this creates the need for numerous translations between interlocutors. Developers have to translate for domain experts. These, in turn, have to translate their demands to developers. Even among developers, there may be a need for translations.</p>\n<p>All these translations make concepts confusing and the consequences of a lack of mutual understanding can be disastrous.</p>\n<p>The importance of this in trying to arrest a Bug goes much further than compilation errors or exceptions. It goes through the swampy characteristic that the software may be logically working almost perfectly, but it is not delivering what was expected by the stakeholder.</p>\n<p>Let's look at the following pieces of code:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>order<span class=\"token punctuation\">.</span>status <span class=\"token operator\">!=</span> completed<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n     <span class=\"token comment\">// (...)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>order<span class=\"token punctuation\">.</span><span class=\"token function\">isFullyRefunded</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n     <span class=\"token comment\">// (...)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Here we have a small example of two ways of expressing the same situation. In the second piece of code, a common language between developers and domain experts was used. Here, possible logical errors can be easily noticed, without the need to keep in mind what the possible states of the Order are and what the expected state is for that case.</p>\n<p>See the following code:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token class-name\">Payment</span> newPayment <span class=\"token operator\">=</span> payment<span class=\"token punctuation\">.</span><span class=\"token function\">add</span><span class=\"token punctuation\">(</span><span class=\"token number\">10</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>What do you expect from this code? Does it add ten dollars to the payment? Ten transactions, inbound or outbound? Will the method create a new payment object or will it just change the value of the payment variable and create a new reference called newPayment?</p>\n<p>A lesson also taken from the book \"Clean Code: A HandBook of Agile Software Craftsmanship\" shows that clear communication must also take place between software developers. Functions must communicate exactly what they are doing.</p>\n<p>If you have to look at the function's implementation to know what it's actually doing, that's a big sign that you need to rename it.</p>\n<h2>Bug is in jail</h2>\n<p>I'm very grateful that you've made it this far, you've certainly learned a lot about police activity and how some of its principles can be applied to combat and remove Bugs from your software.</p>\n<p>By applying this knowledge you will be able to work more confidently in your code, giving you peace in your daily life and also allowing you to experiment and discover new things.</p>\n<p>Your aim will be improved and you will get straight to the point, avoiding damage instead of first looking for the culprits. You become bolder and go further because you know you can trust your tools. This leaves you more time to refine your code and seek excellence.</p>\n<p>Finally, not only will your code be safe and efficient, but its result will also be in line with what your customers expect because through effective communication you can accurately deliver what was demanded from you.</p>\n<p>The result of all this is that Bug is in jail, lives are no longer in danger, and you can go home at the end of your shift to enjoy the rest of the day with your family which is looking forward to welcoming their hero back.</p>","fields":{"slug":"/bug-police-officer/","tags":["debugging","testing","TDD","DDD"]}}}]},"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":"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"]}}}]}},"pageContext":{"slug":"/openapi-journey/","tags":["auto1","engineering","openAPI"],"category":"Engineering","author":"Mariusz Sondecki","date":"2021-11-22"}}