{"data":{"allMarkdownRemark":{"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":"cfee7911-e765-5a6e-ba90-1eb2ce96d837","frontmatter":{"category":"Architecture","title":"Application Cockpit Model Outline","date":"2022-10-28","summary":"A short trip around the AUTO1 Application Cockpit data model","thumbnail":null,"authorName":"Przemysław Walat","authorDescription":"Przemysław is a Expert Software Engineer based in our Szczecin office","authorAvatar":{"relativePath":"pages/application-cockpit-model-outline/avatar_Walat.jpeg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQDAQX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAP/2gAMAwEAAhADEAAAAYNcbctZHqE5AEDf/8QAGRAAAwEBAQAAAAAAAAAAAAAAAQIDEhEA/9oACAEBAAEFAnyRJdo0aBmiW9jE1khCKBOxPVY8/8QAGBEAAgMAAAAAAAAAAAAAAAAAABEBECH/2gAIAQMBAT8Beji//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAHRAAAgIBBQAAAAAAAAAAAAAAAAERQQIgISIxgf/aAAgBAQAGPwJvpqi/CMJg4KTFN7l6P//EABsQAAMBAAMBAAAAAAAAAAAAAAABESExQWFR/9oACAEBAAE/IYU8XDGNg3v6Gar2x1WU9pKgnlpCG63tFjubo1NwXmn/2gAMAwEAAgADAAAAENfwvP/EABgRAAMBAQAAAAAAAAAAAAAAAAABEUFx/9oACAEDAQE/EHENHQ0h/8QAGREBAAIDAAAAAAAAAAAAAAAAAQAhEBFh/9oACAECAQE/EANUy+Z//8QAHRABAAIDAAMBAAAAAAAAAAAAAQARMUFRIWFxgf/aAAgBAQABPxB5rcKC843EOZKEWYB/dSvRVSN3XnMCCmUgo98hrV7b0d+xivco2sPKSmmz7hteDByVo6Gf/9k=","width":50,"height":50,"src":"/static/e4e77cd92050dbb448a088991f2d0c50/d2d31/avatar_Walat.jpeg","srcSet":"/static/e4e77cd92050dbb448a088991f2d0c50/d2d31/avatar_Walat.jpeg 1x,\n/static/e4e77cd92050dbb448a088991f2d0c50/0b804/avatar_Walat.jpeg 1.5x,\n/static/e4e77cd92050dbb448a088991f2d0c50/753c3/avatar_Walat.jpeg 2x,\n/static/e4e77cd92050dbb448a088991f2d0c50/31ca8/avatar_Walat.jpeg 3x"}}},"headerImage":null},"html":"<h1>I’m a model, <font color=“grey”><em>now</em></font> you know what I mean</h1>\n<p>    Welcome, fellow concerned developers. In our <a href=\"https://auto1.tech/application-cockpit/architecture-intro\">previous installment</a>, we have promised to delve deeper into the model that Application Cockpit is using underneath to see how it synergizes with the high-level architecture in order to make the whole product simple yet powerful and extensible. Be advised, that while we have always aimed for simplicity, our initial take on the model was a little bit more complicated, but after more-than-a-few heated debacles, we have stripped it down even more and so far we are pretty happy with it, despite more than a few \"I wish we had the initial design in place\" moments of weakness.</p>\n<p>    In honor of the early version, we are also going to take a short trip down the memory lane to see the trade-offs and compromises we have decided to make along the way and features we have decided to add. We are going to discuss the direction we took and what were our reasons to do so (or in other words, \"what were they thinking ?\"). But for now...</p>\n<h2>Where we're at</h2>\n<p>    As we hinted in the previous blog post, Application Cockpit is actually binary, in a sense that its primary domain consists of only two data types: Resources and Readings and the (extremely) short story is that a Resource is an entity for which we are going to track Readings, while a Reading is a piece of information pertaining to a given Resource. The longer story follows below.</p>\n<h3>Resources</h3>\n<p>    Like mentioned above, a Resource is mostly something that we are going to collect readings for, it can be a physical entity, or a purely logical one. Examples of currently tracked Resources include:</p>\n<ul>\n<li>\n<p>(semi)physical</p>\n<ul>\n<li>\n<p>LambdaDeployment</p>\n</li>\n<li>\n<p>RdsInstance</p>\n</li>\n<li>\n<p>ServiceDeployment</p>\n</li>\n<li>\n<p>ServiceInstance</p>\n</li>\n</ul>\n</li>\n<li>\n<p>logical</p>\n<ul>\n<li>\n<p>Lambda</p>\n</li>\n<li>\n<p>Service</p>\n</li>\n<li>\n<p>SoftwareDependency</p>\n</li>\n<li>\n<p>SoftwareDependencyVersion</p>\n</li>\n</ul>\n</li>\n</ul>\n<p>The Resource has a very rigid model, comprising of only a few, well-defined properties, that is:</p>\n<ul>\n<li>\n<p>id -  the unique UUID</p>\n</li>\n<li>\n<p>type - the type of the resource, see above for examples</p>\n</li>\n<li>\n<p>name - a textual identifier, unique across one type; needs to be deterministically derived from resource's properties available at runtime (further details in tradeoffs chapter)</p>\n</li>\n<li>\n<p>parent_id - id of the parent resource (if any)</p>\n</li>\n<li>\n<p>created_on - a timestamp and a late-comer to the party, but we find it useful in many ways, especially during troubleshooting</p>\n</li>\n</ul>\n<p>In order to publish (a.k.a create) a Resource, the collector will provide only the name, type and optionally the parent identifier.</p>\n<p>When it comes to type, it is a free-form String. We have no predefined enumeration to represent it and the collectors (see previous article to learn more about them) are free to push whatever types they want, akin to the front-end which is allowed to ask for any type it wishes.</p>\n<p>In addition, the inclusion of parent id allows us to build hierarchies, so that our data closely follows the domain that we are modeling.</p>\n<br>\n<h3>Readings</h3>\n<p>    Given the above, you should now have a general idea of what a Resource is and how we use it, so we can move on to Readings. Since we have established that it is generally a piece of data pertaining to a Resource, let's have a look at its properties:</p>\n<ul>\n<li>\n<p>id - the serial identifier;</p>\n</li>\n<li>\n<p>type - type of the Reading; free form String, akin to Resource type</p>\n</li>\n<li>\n<p>resource_id - uuid of the Resource this Reading describes</p>\n</li>\n<li>\n<p>value - free-form json document to hold the data of the Reading</p>\n</li>\n<li>\n<p>storage<em>mode - determines the behavior when multiple Readings are published with same resource</em>id and type:</p>\n<ul>\n<li>\n<p>SNAPSHOT - only the most recent Reading is preserved</p>\n</li>\n<li>\n<p>TIME_SERIES - all of the published Readings are stored</p>\n</li>\n</ul>\n</li>\n</ul>\n<p>Since the values we store for Reading are free-form JSON, it gives developers a lot of flexibility in how to handle their flows. We store both trivial things, e.g.:</p>\n<p><img src=\"https://lh5.googleusercontent.com/v1PbZaRZQJjKJbBQiQZOXRUGjLK_c000j3edWpNb7jhim2dDHl21ircQVV4-aabJnHA57-A68J7WXfTkMFFxeVro0kGAlSMh2PKZ72yW5IezNvN-FiM4Xm-siC7PhAS8ZarnG4imewzKJhjKxAR7Mt0A3R5BOkpSCDYW14ldqG2CLpW5XC-jbwJtvg\"></p>\n<p>To the humongous too-big-to-fit-in-the-picture guys:</p>\n<p><img src=\"https://lh3.googleusercontent.com/FddBhrlKYHmv4Jw9mucH_Mzo0OUs7kbDEN2xOLqfPm7NCyNcM0fxD0MFSSQHODLDWYL8x-CrwCAj-nTJdeqbZGNk-5MrRwxrg7FI4ILuTQTQe8uEPG7OqHLrRZHnuhnjC9Os3x7KcBEDCaQWGJKk_amQo8GnfBh4PK87vfL6cgGD8buX61uiYEIclQ\"></p>\n<p>Again, akin to Resource type, the Reading's type and value are completely opaque to most of the Application Cockpit infrastructure, so individual contributors are free to ship anything they wish in those properties. The only components which need to be aware of the exact structure are the Collector that is originally publishing the data and the UI component which is querying the Data Provider in order to render the respective view. That way, many developers are free to add their own custom extensions, without stepping on each other's toes or needing to seek approval or assistance from the core Application Cockpit team, which in addition can remain blissfully unaware of the atrocities that the others may commit and attempt to hide within their custom Readings.</p>\n<br>\n<h2>The tradeoffs</h2>\n<p>    Having dealt with the detailed model description, we can now move on to discuss how our journey with its design looked like. Of course we, being the pragmatic, more-than-once-bitten little members of the software engineering club that we are, were under no delusions about what is going to happen to our nifty design when it gets into its first bar fight against the necessities of real life. The only question was when we are going to be faced with our first moral/design dilemma and how much are we going to be willing to compromise our collective conscience.</p>\n<br>\n<h3>The Resource id conundrum</h3>\n<p>    When it comes to the little trade-offs, actually, the more observant reader might have noticed by now that something is a bit amiss in our picture:</p>\n<ol>\n<li>\n<p>In this article we have learned that in order to publish a Reading for a Resource, the Collector probably needs to provide the resource_id to bind it to the right entity</p>\n</li>\n<li>\n<p>In the previous article we have mentioned, that Collectors are generally stateless and have no access to the storage holding Resource data</p>\n</li>\n<li>\n<p>In consequence, how can a Collector find out the correct id to publish the reading for ?</p>\n</li>\n</ol>\n<p>Well, it  would that it could, but it can’t, so it shan’t. It’s quite a bind we find ourselves in, right ? We were adamant about not allowing collectors access to our DB, so we had to find another way to make things happen… A short look at the ReadingPublishMessage model, might provide some hints, though, so let’s start with that:</p>\n<p><img src=\"https://lh6.googleusercontent.com/mEaBTCaWa1lWEc0cjQraEWVOPMb3V8StdxwrMvaALgxSxMBfjNjk4KBCRif-F_2oUVzgCBheGOpyLiAK5tnxPcH1m2Nae-8EMyi3CgOhoMUSSVAUJY8jFyckMLAq1PMhPI6UPE_uHSEeCy4yNrpX-mPx4he1VywC5lCtkwJBaC77Px36aqiMivkwig\"></p>\n<p>Does anything stand out to you ? It is almost a reflection of a Reading, but with a slight twist, and you probably got it right, so enter  Resource's name - that tiny little property looks like it has been put there in order to provide a human-friendly name for a Resource, but it couldn't be further from the truth than that. In fact, initially it was never there (we use Readings to store human-friendly names of the Resources anyway) and we added it specifically to solve the issue at hand.</p>\n<p>If you recall, the description says that it needs to be unique and possible to deterministically assemble from the data available in the runtime. Leveraging that, the Collector doesn't need to know the right id, it just needs to provide the correct textual name. The good news is that we have a way of overcoming the initial obstacle, but there is also bad news... This forces every collector to be aware of the logic required to construct names of the Resources it is interested in, moreover, the logic needs to be in multiple places and it needs to be synchronized. But let's leave it at that, since data ingestion is going to be the focal point of the next article in series.</p>\n<br>\n<h3>Can I haz a Resource ?</h3>\n<p>    At this point a meticulous reader with a penchant for domain modeling might begin to wonder:</p>\n<p>\"All right, a Resource can have a parent... That's nice, but is that really all that there is? You say you want to closely match the domain, but not everything is a child of something, sometimes you might want a Resource to 'own' Resources, so... how about that, smarty pants ?\"</p>\n<p>This was also something we were intensively brainstorming about and lots and lots of coffee cups later (and probably more than a few beer mugs, too) we have arrived at something we were not very uncomfortable with. Our main priority was to avoid complicating the model (remember ? we want people to integrate easily, so we want to keep things simple), so we didn't want to introduce any new concepts or tables. Instead, we elected to cheat a little, so please have a look at the example below with a picture being worth a thousand words:</p>\n<h2><img src=\"https://lh3.googleusercontent.com/9ofjHjm_CHNEKWH-7C6kdE5aq9ELbeH4ekpOoOZcDMJtwYjP3xWOTiceE3kW1i0pl4H3uTCbh0c5eY1MMqpWJRXjB0J4L66x0vf6YHhs15fY-s_oA-8qobF_eynejn8xlsns09OlmgIGAy7bZNDDebFodPEHmGrPgp8HnAhoCCUIzEi7fV1OWheP\"></h2>\n<p>Basically we have elected to model a \"has a\" relationship between two Resources using an intermediary Reading to store ids of the referenced (one or many) Resources. In our example a Service Deployment has a Reading Called \"Service Deployment Dependencies\". The main purpose of this Reading is to hold data which we can use to construct id of \"Software Dependency Version\" Resources that our \"Service Deployment\" has. As you can see, if we wanted to obtain vulnerabilities of all dependencies a specific Service Deployment Resource has, we could easily construct ids of each and every \"Software Dependency Version\" and then query for its \"Dependency Version Vulnerability\" Readings. </p>\n<p>There aren't any places where we use this not-so-orthodox approach, but there are some and the example below is the most prominent real use-case. We are closely monitoring the performance impact it has on our service and so far, it has been negligible, or at least negligible enough that we are still able to look at ourselves in the mirror.</p>\n<br>\n<h2>and the (mostly) harmless evolution</h2>\n<p>    Having confessed our most prominent sins, let's wrap things up with a less controversial story. There are several items that were not part of the initial design, which we have decided to add along the way, because we have found them useful.</p>\n<br>\n<h3>To replace or... not to replace</h3>\n<p>    The storage mode for a Reading was a last-minute addition to the picture, but it serves its purpose well. Initially we thought of Readings solely as means of representing the current state of things, e.g. the single-point-in-time data like:</p>\n<ul>\n<li>\n<p>tech stack given service is using</p>\n</li>\n<li>\n<p>AWS account id of the deployment</p>\n</li>\n<li>\n<p>IP address of an instance, etc.</p>\n</li>\n</ul>\n<p>This meant that we were only interested in the most recent value of a specific Reading Type for a Resource, always replacing it whenever a new value was collected. From the more technical standpoint - we could get away with performing upserts on Readings with the key being comprised of resource<em>id and reading</em>type. However, after a few initial releases, we've discovered that there are certain pieces of data more linear in their nature, which Application Cockpit users could really appreciate on a daily basis.</p>\n<p>A prominent example of such an item is Service Events Graph, which as you can probably guess represents a service's life cycle timeline, so a mixed bag of Service Deployments, Instance Startups and Shutdowns, Incidents etc. Here's what it currently looks like:</p>\n<p><img src=\"https://lh4.googleusercontent.com/x_T21RH2LmaVQypUzhNgve4ZEPeKBTvWs0IXdJdPF_RVz3EXAb3SK09ZR1JRxmt6OUSTQdRpowIdPGUI0A17-ATiLE-nSY3u8hA0OjZZMO9HF-RCBw9q0I5GG_xIEwMyMy2g5OpxWc7QTmN2KnZy29jJ8pTdt0Av3MNQ4FuKS0e7FT3aRuYZv1diWA\"></p>\n<p>What we have also found out rather quickly was that it would be quite awkward to represent this kind of data using our initial point-in-time-based approach to Readings.</p>\n<p>Enter storage mode - a property of a Reading which tells the underlying infrastructure how to behave when a new Reading for the same resource<em>id and reading</em>type is persisted. </p>\n<p>To preserve all the already existing flows, we have assumed the default mode to be SNAPSHOT, which upholds the default behavior of upserting new values, keeping all the implemented features intact without any additional effort. </p>\n<p>As an extension of the functionality, we have introduced TIMESERIES mode, which preserves all the values that were collected and allows us to construct e.g. neat timelines like what you can see in the screenshot above. </p>\n<p>As a bonus, here's how the actual data underlying the graph looks like:</p>\n<p><img src=\"https://lh3.googleusercontent.com/HsXSzSFwhynsa77tvxtyWg2K54pJpTXvXMghLmselpYCFLk7vktBkwWlQ5q7T_WpTiEYSTi85ULtxxz5fNhfleOVEhXf5NRm_51YPAh9x_t1aI53KyHs8QA1bRqLuhjMtZeSXwQIRrOq8rI2FxIxFs2bktIuLclo1ZgQN4sGEcdEKrrW-ZzE10tRHQ\"></p>\n<h3>A little housekeeping never killed nobody... I hope</h3>\n<p>    Whilst it seems quite natural for a Reading to have a timestamp since it is collected (or, as you will, measured) at a specific point in time, it's not so straightforward in case of a Resource, especially not from the Business Domain side of things, since it mainly represents a certain Entity that simply is.  Seems like we're not all about Business, I guess, but we'll get to that in a while.</p>\n<p>With the above in mind, the first iterations of the Resource model did not have a good reason to include timestamps, so they didn't. However, as we kept adding new features and collectors, we have started noticing that the system is accreting quite a lot of Resources and that a rather large portion of them is only relevant within a short timeframe. It wasn't a problem for us at that point, but even though we hate doing the house chores as much as we do, why not keep our room tidy and \"let go of the things that don't matter anymore\", as we've all certainly been told at least a few times in our life ?</p>\n<p>The part where it most stood out for us were Service Instance resources which by their nature are short lived, since as soon as the instance is dead, we don't care about it anymore (apart from audit reasons perhaps). The point was - how do we know whether we can safely delete the Instance Resource ? </p>\n<p>As you have seen above, we have ServiceEvents telling us when instances were terminated, but can we rely on the event to always be generated and are we willing to trust it ? Not really.</p>\n<p>Alternatively, we do track instance health checks but they are not ideal by themselves, particularly since a freshly minted Instance would have no checks at all in the beginning and then report as unhealthy for some time before it becomes stable, not to mention that in some cases it is all right for an instance to fail some of the inspections for a while and still survive. The list of things to consider goes on and on, which went contrary to our goal which was to avoid creating a too-sophisticated cleanup-logic for fear it would become too brittle to be effective.</p>\n<p>The introduction of Resource timestamps allowed us to reach all the objectives in a relatively low-cost and simple manner. Once we started tracking when an Instance was created it was a simple manner scanning Instance Resources on a schedule, discovering those which had no health reports for quite a while and were not created recently to safely trim them away from our storage.</p>\n<p>The added bonus is that having a Resource creation timestamp comes in handy during investigation of issues (not that there are too many), since it makes it easier to track the timeline and wrap your head around what happened when and why.</p>\n<br>\n<h2>That's all, folks</h2>\n<p>    At this point I am going to conclude my musings and I hope that you've found them interesting or perhaps even inspiring at some point. The topic is far from exhausted, especially given that our journey with Application Cockpit is still well under way and we keep on adding new features into the application, so please look forward to the next article in series.</p>","fields":{"slug":"/application-cockpit-model-outline/","tags":["auto1","Cockpit","DataModel","Architecture","Core"]}}},{"node":{"id":"d980aa96-d832-53e1-a6c2-0f3710beb58a","frontmatter":{"category":"Engineering","title":"Application Cockpit Architecture Overview","date":"2022-02-17","summary":"An overview of AUTO1 Application Cockpit architecture and its most notable features","thumbnail":{"relativePath":"pages/application-cockpit/architecture-intro/thumb.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAADfUlEQVQ4y61UW2tcVRQ+OyAiiIIRVBRT6UPibmIcze2cOff7LZOZzORq0lorMiQxzjgdxQfFB6VFyJPSWKrRNE2wIW3BovlrHrrZy7X3MJGCjxn4OGuvy7e+tfY5oyjn/dN27hNj55ioP9wnMzePycjX98jQF/vkQvcuudA5IIMbe2Rg/WfywrU98lrzgLzePCSX2ifk3c5DMvrZAzK8cUJeXjsiz1TvEpLvk3MXqOjf741o3/1G3//mDvVvHNDxr+7Q4c5tOtb9hY53f6VvNG9RpXKDksYOHVz/ib5yZZeOb+/TydY9Otrcp8Mf/06fr2JO+CN9NtulinXr8ZPo9inzd0+fvHPzIbv47TF79csjNvj5IXupdche/BSxecTe7Dxgb3UesaHWI3ax/Se71P2LjV0/ZW+3/mZDzcfsuaUTpuR/MGXqw21Qr7Zgcm0TSqub+NyG91a2YHRhA7EFY40tKK+1wb5yHdRlzF1ugbrUgpnFFmhLbVAXEQttmJ5vw0i0BUpkWzwLPB6YOne1ae7pM2cIDJX7psotdZK7aEeuwV30W+oEt7UpbmmTEnZZYIp7xjRXoiSB+UYDstkK2K7Xg+Oe2Y7lgWFYkOWzsLL6AURJCkmagReE4MUBxHh2vQAckYtQgiCAWq0GYRiC67oQRREIn7Bdz4VwIwY7tiHAojRN0OdBks/1YnkEOTbyfR8cx5E1im3bkkw4+nYcx9IWPm/RB9M1ZXKC03hYnGaZjAtyz0d1ji3PAopIStMUTNOUDsuyzmwJ3QZd16Xqer0u1YgcERN5Av2zJIzSCsS4kzCKIUxyqeopuA6YlimJxDrkKhCeGB3FiIn665Iju54PPnYXCX7QC4gEUdwfXxALW6jsNxKjr1++Cg280KcIsYDHUcRRLsddcCTm1WqVY3eJLMs4JnLXcbjvudw0DG6aBrctiyMZxyZc18vcskwJRexPSO93Ft1E1/5OhA8J5OtRqTVQGY4Yp4gMLFvcbG+yIIxxLQEoWPAPKijwWSBBgR0LVCVt4ceCAlUVUV4vVj/pFrXlj4q5xctFbeVakVbqRZxXi3SuUVQW1opkdr5QNE0bx1sqCSBJqVwulyYmJqSNNyj9hmGgXyvp2rT06WW1VFaFbci4eBqGLmPn//eFrAOoZgA7/S9EDBVK4Kc1gJ8lwukBYz38dz53gf8CwA4PmL99534AAAAASUVORK5CYII=","width":325,"height":325,"src":"/static/b08a403a7e9534ad64f67f1ad61141df/b3029/thumb.png","srcSet":"/static/b08a403a7e9534ad64f67f1ad61141df/b3029/thumb.png 1x,\n/static/b08a403a7e9534ad64f67f1ad61141df/8d141/thumb.png 1.5x,\n/static/b08a403a7e9534ad64f67f1ad61141df/ee72c/thumb.png 2x,\n/static/b08a403a7e9534ad64f67f1ad61141df/5dfa8/thumb.png 3x"}}},"authorName":"Przemyslaw Walat","authorDescription":"Przemyslaw is an Expert Software Engineer based in our Szczecin office","authorAvatar":null,"headerImage":{"relativePath":"pages/application-cockpit/architecture-intro/1.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACaUlEQVQoz0VSS08TURSelQtXrkhI1JjYhQuIIZEC7bzuvTN3bjtDH5TSQlqUBGGlKOGZCOnWP0Di2oAJCTHEgH9O5/Oca6csvsy55/Gdc74zTnjx60/y7Teii/t87utPlEbXmD6+wtTBJab2r/DkwyUe737H9N41nn+6wbOPNyh9vsXM4R1mD+7xau8OT9/f4lH3Gk76A86b3ROUd0/z8s4pZreO8HrrBDObxyitH+Bl/xCl3hHmt85Q3R6h/PYLyptnmB+eY35wjsXhCAsD8m+MMLd2jhe1EziJknlmEqjAy4VbQRR4UH4VEUGHHmLhQrhLiMnWMoDwKggqCwiriwiqCxMIdxHSX4JjanV0VrvIlpcRCkkQCEIxsUNXwnV9JKaG7lrPfqM4hlQRpI4Q6wRCqnG+hBNTsN1uQ2sNKSWSJAH72JZKQu8kCHSIOIpRr9fJr5BmTahIQTcSNBpNRFEEQWRc4wiagkn4wRMxsTEGgqbkWDSM4SvfEmZZRsX0bbQQhiE1VDSttnkFnGRM4PsegsAnBGT7tsCiGsKtuig2iYikiHEeg2sKn2No/HqakRYaupaNO8mHrjS5T42YsFarQREh+/lbpzdvNJGIV5a0ApOx0JE25BQ2gRMZLAd3LgqZjAsVHaW/McDKSmcimSU0JsnHRbmgQkUCt1otewBGmqZ2GiZVdCQrC60pRIher2/JXNe1cV7d4SJaJS9W5Ek6nc5EE/Z5nke/i0G63LRy8CEUoYjz5flA/1eW8i8bFMg5gdfiaz4kk4aeiyRrY317H43uEI3VAVq9dzBpE9qkMFkLWbtv3/8ADCO9cUtyqcEAAAAASUVORK5CYII=","width":1280,"height":786,"src":"/static/477fbbb52f4a6b56a8627202336a0dce/26421/1.png","srcSet":"/static/477fbbb52f4a6b56a8627202336a0dce/26421/1.png 1x"}}}},"html":"<p>Hello fellow concerned developers, or, if you are not yet concerned, while it is not the crux of today’s entry, I hope\nthat by the end of it, you might get interested in becoming one and/or find out how you could do that on your own and\nwhat it actually means for us. Still, the focal point of this post is to fill you in on the architecture of Application\nCockpit (or “App Cockpit”, as we call it in private), the newest tool AUTO1 has brought in for its concerned developers.</p>\n<p><strong>The “why”</strong></p>\n<p>However, before dwelling on the “how” too much, let us pay homage to propriety and start with the ‘why’ of the whole\nmatter. The main reason for the emergence of the Cockpit stems from the plethora of tools AUTO1 has adopted to help us\ntrace, measure and monitor our services, not to mention alerting us when things go south. This opulence often means that\nif you are looking for a specific bit of information, whatever the reason, chances are the data is there somewhere, but\nyou need to make a significant effort finding out where to look for it first.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/7913f56e8ec1a8e1392f735128cb79a1/ea190/2.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: 37.099494097807764%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsSAAALEgHS3X78AAABLklEQVQoz12P626CQBSE9xlUKO+g9YJGQRFhuUbFhED7/q/RNDnTnZPSH/1xwpyZ2W8XMw7D9ziOsDYXay04RVEgz3MdanrUUzb1mqZB27Yoy1KqqoKbLzN+fOLx7KQoK9zvd5p6mLrrOtX0Xq+XAggihB7h7P36wt2UbSf1Y0Ca0bC4XC44nU6o61qL1GmaKvh8PiOKIoURzOx2u6Hve8myjJmYrGiQla0Lj3+/xSILk06SRIeaQILjONb9er3qBQQzM0XzFFs/WNInTwE1h5ovpE8IYbyYIO68yOXC3P2dmP0xxiFK5H29xn4fYrvdYrlcYrfbYbPZqOY3DEOsVisd7mvXn3qEO7DQM54fiPcWYD6fi+d5WCwWmM1moPZ9XzU9juvo/M+CINCzZPwA2yvoGlsGHf4AAAAASUVORK5CYII='); 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=\"2\"\n        title=\"\"\n        src=\"/static/7913f56e8ec1a8e1392f735128cb79a1/40fad/2.png\"\n        srcset=\"/static/7913f56e8ec1a8e1392f735128cb79a1/707e9/2.png 148w,\n/static/7913f56e8ec1a8e1392f735128cb79a1/649e0/2.png 295w,\n/static/7913f56e8ec1a8e1392f735128cb79a1/40fad/2.png 590w,\n/static/7913f56e8ec1a8e1392f735128cb79a1/b3fef/2.png 885w,\n/static/7913f56e8ec1a8e1392f735128cb79a1/301c0/2.png 1180w,\n/static/7913f56e8ec1a8e1392f735128cb79a1/ea190/2.png 1186w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Once you have figured it out, more often than not, you are invited to dance your way around Kibana, GitHub and Grafana,\ncorrelating your findings, perhaps with a touch of Consul and AWS Event Log to help you along the way, sprinkled with a\nChronograph query here and there to spice things up. Neither is it fast, nor easy, nor pretty. What’s worse, we have\nnoticed, that as our ecosystem grows, it gradually becomes harder to know what to look for and where to find it and the\nlearning curve for new hires has started growing ever steeper.</p>\n<p><strong>…. and the “what”</strong></p>\n<p>Enter the App Cockpit. In short, the Cockpit is meant as THE tool which lets concerned developers quickly find their\nbearings and gather relevant information pertaining to a particular service running in our ecosystem. Being concerned,\nactually, for us means that you care about your services and the ecosystem as a whole and you want to keep your\napplications in tip-top shape as well as you feel responsible for troubleshooting them when needed.</p>\n<p>Keeping that in mind, we’ve designed the App Cockpit to be the first place you check when you want to find out who owns\nthe service, what other services or resources it relies on, is it healthy or not and what its main software dependencies\nare. It is also meant to be the place you visit first when a service starts misbehaving to be presented with quick\ntroubleshooting aids, or, if you are the owner, to perhaps bask in glory when everything runs smoothly.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/819dad8db10b5028167d822d8695e0db/a1751/3.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: 53.18118948824343%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsSAAALEgHS3X78AAAB8klEQVQoz1VSy47aMBTNBgYJ6I8gpFJAIkBC4tjOO6GESqxowqKfMMNuRuq0/zq7foFPr02CmMXRffjc63t9bHmeBy5CxblAEAQGvu+Dc27Q+YwxaG6H3W4H13U7qDb+Z+33e5UXBRG8R8IdO/dWaNs2lsvlHeaMmjxAbTYbWGEYQkqpWMDgBz4CFoC1k2rf574pqH/WuF6vuL5c8fzyDBlKM72pY77iAcd2u1WWJz1KMDjCAQsZmM8M0YAu4Ckz69a/Grz+ecX7+2+8/X1DkiRwmANxCCATAY972Gz1hHGIUErwkCOOYwgh9MQQGqFAksYm73Ja3Xex3thYbVeIoggiEoj2IeI8pqYSjuvAKrICUfYdaZpAxIJsijiJkWUZ+YQsN7mSbJkXyPMcUkgztXkesxFDuzIsMw0RHhXU0Ct7Pvnt+rqBhn4eX5+3nEcYUQpSuCToRKdc9y06XxNXq5VR2taWYpumMXGL9XptYB1/HEmxWOl31Ip3f0/7+j11w9PphMvlgrqu0ZBtKG6ormmaW65p1Pl8huM4pHJaqenXb/S3FmaKxWKB6XSK+XyO2WyGyWRimldVhcPhgOp4REUKH+iye45sWZZKc63hcIgv47EajUbQvrbj8fiTPxgM0Ov10O/3b/bpCf3POaUt8T/+A9K1gDWcRDsxAAAAAElFTkSuQmCC'); 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=\"3\"\n        title=\"\"\n        src=\"/static/819dad8db10b5028167d822d8695e0db/40fad/3.png\"\n        srcset=\"/static/819dad8db10b5028167d822d8695e0db/707e9/3.png 148w,\n/static/819dad8db10b5028167d822d8695e0db/649e0/3.png 295w,\n/static/819dad8db10b5028167d822d8695e0db/40fad/3.png 590w,\n/static/819dad8db10b5028167d822d8695e0db/b3fef/3.png 885w,\n/static/819dad8db10b5028167d822d8695e0db/301c0/3.png 1180w,\n/static/819dad8db10b5028167d822d8695e0db/a1751/3.png 1446w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>To achieve all that, the Cockpit needs to pull data from multiple sources, aggregate it and present it in a comfortable\nand meaningful manner to the users, not to mention being fast and efficient about it, which leads us to the heart of the\nmatter - the architecture of App Cockpit.</p>\n<p><strong>Finally, the “how”….</strong></p>\n<p>When designing App Cockpit, we were striving for what probably every software engineer worth his hide can recite even\nwhen woken up at 4 am in the morning - we wanted the architecture to be “open and easily extensible, yet simple and\nrobust at the same time\", preferably allowing polyglot extensions to let every team in the company contribute and share\nwith others if they came up with a cool feature. In order to achieve this, we have split the Cockpit into 5 logical\ncomponents distributed in 3 different layers, as depicted below, with the descriptions to follow.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a5fde0a614ee1d33cf9b3d2224330a72/b6c2c/diag-1.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: 382px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 178.79581151832463%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,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'); 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=\"diag 1\"\n        title=\"\"\n        src=\"/static/a5fde0a614ee1d33cf9b3d2224330a72/b6c2c/diag-1.png\"\n        srcset=\"/static/a5fde0a614ee1d33cf9b3d2224330a72/a384c/diag-1.png 148w,\n/static/a5fde0a614ee1d33cf9b3d2224330a72/9ca5b/diag-1.png 295w,\n/static/a5fde0a614ee1d33cf9b3d2224330a72/b6c2c/diag-1.png 382w\"\n        sizes=\"(max-width: 382px) 100vw, 382px\"\n      />\n  </span>\n  </a></p>\n<ul>\n<li>\n<p><strong>Data Collector</strong> - the component responsible for fetching data from multiple external sources, parsing, aggregating,\ntransforming and sending it on its way to the data sink with a tiny stretch it might be called the ETL of App Cockpit</p>\n</li>\n<li>\n<p><strong>Data Sink</strong> - a simplistic component with a one-track mind, whose only job is to receive data in a well-defined\nformat from the Collector, occasionally perform some simple and generic operations on it (like basic validation and\nparent id resolution) and persisting it in the data storage</p>\n</li>\n<li>\n<p><strong>Data Storage</strong> - a simple storage that holds the current snapshot of App Cockpit data</p>\n</li>\n<li>\n<p><strong>Data Provider</strong> (aka Backend For Frontend) - exposes an interface to serve everything our UI needs, both serving the\ndata “as-is” from the storage as well as applying some additional logic to further aggregate and transform it for\nspecial purposes</p>\n</li>\n<li>\n<p><strong>UI</strong> - yer plain old Front-end - by design contains no logic, it only displays the data as provided</p>\n</li>\n</ul>\n<p><strong>Sweet, but how about a model?</strong></p>\n<p>The specifics of the model run outside of our scope, however, getting it wrong could severely hobble the architecture,\nso it is a story in and of itself, which will have to wait its turn to be told in another blog entry. For our purposes,\nwe will only briefly touch upon it, mainly to establish the domain.</p>\n<p>Truth be told, App Cockpit is pretty much a simpleton, since its world consists of only two kinds of things: Readings\nand Resources and everything it does revolves around the pair.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/620475cb60f40b22eca2eb0e91489dcd/bdb41/4.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: 60.852459016393446%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsSAAALEgHS3X78AAABtUlEQVQoz3VSbZabMAzkGkl+5ZVDYMDG+AvsBBfSvf9F+mpVEssub7v9MRGRRiONoLrf77/ruob6R11utxscuF6vcLlcOJ7xb4655brn/1TBB1jyUkKawBoLxhiw1sI8z5BSAu89OGc5hhA4RzXnHPMs8qdphpieRY8jkGDZXhv44EvOmUnc+HggaUd6ZliWhUVJ8PV68WDKEbCn/Hp7g2EYSqWUYhEpJU8l4oiTDG4rlQY1aNDGgdaaOVQj4aOPeiQ+O+9AYb2i9WnKuq5shYg4iaE5qncMHzhzjmeKNLAiC9u2QUQxEqTpZ0zTxCdYlifWJ97oK4dAveSCLcfHAi7M0Pc9TyFQvus6vGViUa1HeOQNBowH5wBtmH9m7qmk7GH0CbSbQTQNCCF2tAIa/D9aw1u1KO7iCqOLn5wPbgshTrtl+jEBBU3AYgMtFg+IRoD1lg/e9/jSYgaJL+nMYeCwOeH9JW8oCwmOPpYWp/1PsMNz+LTCYDzWxDeCcRdU+4bFoG3xRZAskyAdvet6sHNGYclOzpaJG+awW1ZKltF6MH4qdNQz+Bx4Q/o2z5/Hd6Aa8f8CyFeeSMaQMngAAAAASUVORK5CYII='); 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=\"4\"\n        title=\"\"\n        src=\"/static/620475cb60f40b22eca2eb0e91489dcd/40fad/4.png\"\n        srcset=\"/static/620475cb60f40b22eca2eb0e91489dcd/707e9/4.png 148w,\n/static/620475cb60f40b22eca2eb0e91489dcd/649e0/4.png 295w,\n/static/620475cb60f40b22eca2eb0e91489dcd/40fad/4.png 590w,\n/static/620475cb60f40b22eca2eb0e91489dcd/b3fef/4.png 885w,\n/static/620475cb60f40b22eca2eb0e91489dcd/301c0/4.png 1180w,\n/static/620475cb60f40b22eca2eb0e91489dcd/bdb41/4.png 1525w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Accordingly, a Reading is a piece of data that describes a Resource in some way, e.g. a metric value, service name,\nowning team, the version of Docker image it currently runs etc.</p>\n<p>In complement, a Resource is a concrete entity for which Readings can be collected and then served; it can be a physical\nentity, e.g. an instance of a service or a logical one (e.g. a definition of a service, e.g. “authentication-service”, a\nsoftware dependency e.g. ‘log4j”, etc). In addition, Resources can form hierarchies, since they support a\nparent-children relationship.</p>\n<p>Given all the above, the main purpose of the App Cockpit is to collect Readings for tracked Resources and present them\nto the users.</p>\n<p><strong>Getting physical.</strong></p>\n<p>Having established all the basics, we can now shift our focus to the physical application of the architecture. We will\ngo over logical components one by one and discuss how we have decided to implement it, to finally sum it all up with a\nfull diagram illustrating the complete solution.</p>\n<p><strong>UI</strong> - we have chosen to implement it using a customized version of Backstage app, built using React JS and available\nat <a href=\"https://backstage.io/\">backstage.io</a>. Since it provides many handy features readily out of the box, it has greatly\nstreamlined the UI development process. Provides a web interface for the users, communicates with Data Provider over\nHTTP via REST endpoints.</p>\n<p><strong>Data Provider</strong> - a simple, easily scalable service built in Java using Spring Boot, exposes REST endpoints for UI\nwhile fetching data from the datasource over JDBC; Apart from serving Readings and Resources it also has a concept of\n“calculations”, which come into play when returning plain entities is not enough to present the user with the desired\ndata. Calculations encapsulate any form of additional aggregation, correlation and transformation logic that is\nperformed on the data in order to enable the UI to present more complex views without placing actual logic in the UI\nitself.</p>\n<p><strong>Data Storage</strong> - nothing fancy there, while we have briefly considered using a temporal db, we have decided to fall\nback to plain old PostgreSQL, since the Cockpit is operating on relational data; the decision has paid off, though,\nsince we were able to leverage the built-in JSONB type to store heterogeneous Reading data in a single column. It is a\nsimple, yet powerful mechanism, which makes the system extremely pluggable. Since a Reading is actually a free-form JSON\nobject, all throughout the ecosystem, the only components that need to know its exact model are the Collector\nimplementation that is going to publish it and the actual UI component that is going to display it. Every other piece of\nthe system can be safely kept in the dark and will be more than happy about it.</p>\n<p><strong>Data Sink</strong> - implemented as a simple lambda function, it has a well-defined job to do; it listens to SNS (AWS'\nmessaging product) events published by the Collectors to its proprietary topic, extracts the payloads, performs simple\nvalidation logic and persists them in the storage. In addition, it is also responsible for properly resolving parent\nResources for incoming children. This piece of logic would fit in the Collectors as well, however since we have decided\nto make them stateless, it has inevitably found its way to the Data Sink.</p>\n<p><strong>Data Collector</strong> - it is the only component for which the logical diagram differs from the physical one, albeit by\ndesign. This stems from the fact that the Collector has been meant as the main extension point that other teams,\npossibly using disparate stacks, can easily plug into when adding new features. By all means, a Collector, as previously\nstated, is any component that fetches data from an arbitrary source, transforms it into Resources and/or Readings and\npushes it to the Data Sink SNS topic.</p>\n<p>In consequence, when you want to start collecting a new type of data or start collecting it from a different source, you\nhave two options - either add your implementation in one of already existing collector services, or write your own new\none, and the only requirement we superimpose on you is that when you publish to the Data Sink, you follow the format we\nhave specified. At the time this post is being written, we have 3 distinct Data Collector implementations:</p>\n<ul>\n<li>\n<p><strong>Main Collector</strong>  - a general-purpose collector, written in Java and providing data for cross-platform features,\ne.g.:</p>\n<ul>\n<li>\n<p>service ownership</p>\n</li>\n<li>\n<p>incidents</p>\n</li>\n<li>\n<p>Docker images used and their vulnerabilities</p>\n</li>\n<li>\n<p>service health and deployments</p>\n</li>\n<li>\n<p>currently running instances</p>\n</li>\n<li>\n<p>code insights (quality, PRs, contributors etc)</p>\n</li>\n</ul>\n</li>\n</ul>\n<ul>\n<li>\n<p><strong>Java Collector</strong> - yet another service written in Java, as the name suggests it collects data strictly related to\nservices written in Java, e.g.:</p>\n<ul>\n<li>\n<p>runtime information like JVM and GC being used</p>\n</li>\n<li>\n<p>selective list of JVM parameters passed to the instances</p>\n</li>\n<li>\n<p>egress dependencies for Java services (in this context, the upstream services)</p>\n</li>\n</ul>\n</li>\n<li>\n<p><strong>PHP/Golang collector</strong> - the first contribution coming from outside the original App Cockpit team, written in\nGolang, it is going to provision data for PHP and Golang services, e.g.:</p>\n<ul>\n<li>\n<p>egress dependencies</p>\n</li>\n<li>\n<p>vulnerabilities</p>\n</li>\n<li>\n<p>possibly many more to come</p>\n</li>\n</ul>\n</li>\n</ul>\n<p>With a picture being worth a thousand words, let’s have a look at a diagram that gathers all of this in one place:</p>\n<p><br><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f576711fa336eca6db48f2c1cb19c47c/b2341/diag-2.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: 531px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 161.5819209039548%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,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'); 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=\"diag 2\"\n        title=\"\"\n        src=\"/static/f576711fa336eca6db48f2c1cb19c47c/b2341/diag-2.png\"\n        srcset=\"/static/f576711fa336eca6db48f2c1cb19c47c/85b06/diag-2.png 148w,\n/static/f576711fa336eca6db48f2c1cb19c47c/75500/diag-2.png 295w,\n/static/f576711fa336eca6db48f2c1cb19c47c/b2341/diag-2.png 531w\"\n        sizes=\"(max-width: 531px) 100vw, 531px\"\n      />\n  </span>\n  </a><br></p>\n<p>Obviously, the actual process of data collection, as it is currently implemented, is a whole story in and of itself,\ndeserving yet another dedicated blog post, especially since we have elected to make all the collectors stateless, which\nhad its numerous consequences and challenges so keep your eye out for the next entry which is bound to come shortly.</p>\n<p><strong>Closing words and … we will be back</strong></p>\n<p>Having said, or rather written all the above, we sincerely hope that we have managed to pique your interest. Not only in\nour solution but also in actually getting “concerned” about your ecosystem, because it does not necessarily have to be\nboring, especially when you have the right tools to make your life easier. On top of that, you can have your fun\nbuilding the actual tools, deftly combining business with pleasure for a change of the usual pace.</p>\n<p>As hinted numerous times, this is only the preliminary article, meant to lay the groundwork for future additions\nexploring individual pieces in more depth… We are already looking forward to writing those, so keep your eye out!</p>","fields":{"slug":"/application-cockpit/architecture-intro/","tags":["auto1","Engineering","Architecture","Cockpit"]}}}]}},"pageContext":{"slug":"/tags/Cockpit","tag":"Cockpit","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}