{"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":"4c23b9b2-0718-52ff-a466-8b87fec6925d","frontmatter":{"category":"Architecture","title":"Machine Learning pipeline","date":"2022-08-31","summary":"Machine learning systems are complicated, but good infrastructure can reduce this complexity by automating processes. Well thought out infrastructure solutions can lower the engineering time and accelerate your teams ability to deliver ML applications. In this blog post, I go over an example of automating a ML pipeline using AWS services, review the structure and give you some action points to consider when creating your own.","thumbnail":null,"authorName":"Alena Nazarava","authorDescription":"Alena Nazarava is a Data Scientist working remotely from Bavaria","authorAvatar":{"relativePath":"pages/machine-learning-pipeline/avatar_Nazarava.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAEgUlEQVQ4yyXRaWwUZRgH8EnEkxTXcrVFzpJSIUBtqdB272Nm53xn3pnZOXZmdmZn9u6W0m5blkKtUIkCtQiklOOL1MolEEBDBQP1SEwUNcaEeEXRT4qJH4x+aiJuNXnyfPs9/+T/ILIiSBKURSDxpASJGMRFFhfYqERjMkAVJsJhHoHwiyDMM0ERhHhQ2WEFYCrAkTks8zGRE3lK4HABRHkGjzFRlSF41P9cjatu/rz6RQtI/zaVQ2U6HGMiMojIEFO4/7EEeQFwHAUZjKMikEJFMqzQGLqpvhxp1FqWzUeQtbULFRBR6YgydzeqcngckoiiirEYFDiGAyRHoSwRAtEARANoW/OIFng4c2D27MB+6Kt5cp4Q9WpsRKZCCo3GWVyH1H9Y4gTIQICzJArQIAi6uZDH29R4plf+/mjXr+OF6e1861IXCLXpHKbQIQ2gOoslBApR4xUMBZ6GLMESERDxg0AHG+7wNa17e8j5c3L3g/HinZLqrnMxnhaDxys+AaOmSFoSU8ExReZlAYgczVEYQH1MyMMF2wMtGw4n8L+m9j4YzV028c1Vj9DuzRVswGgyRtky4ygA0XRFU2OqxEkQCDQBsSAI+dmQh2xvZpvX/jJ14IcRm1heve4JhGxdbwpEZWyJcWQmpbGIXsG6rKliXGQljhYoDEb8pN9NtG9pXVH75cnShURHU9W8zQse9TSssGU6GaMdicmoXNaASCKhGUbc0BRdEeIiUDiSRf39cjQDqRFHnL09OtOLMfWuTdWPN7iqFCacUumUwmR1Lm8KSNI2kqZm6IqhyYYCFUBIhP/GWM/0+clPbt+cPvnauZGSZ9XCjYuekhuWHoz7KuF5XchbQtGWENu2kpZhJuIVr8ucwuKVtq9NvHTvq7vnLr5z5eLV0f7iOtdjgZXPnNHd34zqOYkqJKWiI3WnVcRxbNs2rblwWeVpmSFxd9urw8Off3r3i49nThw9zW5t6lhTJzYuvJbx3j+eLPDh7Sm1Jx3vzWpIOuXYjmVaWqVzGQI+irGhwJtvTL5/68OP7sz0pbM6gUc3rmFWPf1u1nP/dbUoBLszWiln9BUsJJV2ko5pWYmErqoCB6MYj4Y/++DWW5fem5w4PrGrc0cm317r8tdUndWe/208WTZBVyY+0JkodztIJp/J5jLptGMauirwkMRJb8fLI4cuX705NT42NXGEcnt9y6o9tVU3iv7ZSwP78rEdebPcZe8pZZF8MV/ozKVStlGpW2AhTUTat3V3dv/843dnL1w9eOgYumG9/9nFVOPy+8esf64MHOoxejqtod7s0M4ikitk09mUnbR0XZV4AAgMD3hZX3j/zlcOj57aUxp0r65vq1sS9239/Xzfw+uDY/1WqWgP93cODXYhc7HZVNKxdEMRIQA4GnRvdWQ9K+TQTQH36uYtNWs2Vi+Wg94/rgzNXt91sJQYKKZfHOgeHuxF0l2FVCZlOmbcUCHPUATqfqF1b3n3va+/PX3qwokjlVf5W5asDKxd/dNk6e/p4X09dnlHYVdf155y6V+to7XKa9wQBAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/9ff0aff34e80697669a1119f76326bbe/45876/avatar_Nazarava.png","srcSet":"/static/9ff0aff34e80697669a1119f76326bbe/45876/avatar_Nazarava.png 1x,\n/static/9ff0aff34e80697669a1119f76326bbe/eb85b/avatar_Nazarava.png 1.5x,\n/static/9ff0aff34e80697669a1119f76326bbe/4f71c/avatar_Nazarava.png 2x,\n/static/9ff0aff34e80697669a1119f76326bbe/9ec3e/avatar_Nazarava.png 3x"}}},"headerImage":null},"html":"<h1>Machine Learning pipeline</h1>\n<h2>Introduction</h2>\n<p>Developing a machine learning pipeline generally requires a custom approach. There is no off the shelf, well-architectured system that will work for all use cases. On the contrary each use case requires you to design the structure that will satisfy your particular requirements. The process for developing, deploying, and continuously improving them is quite complex. There are three main axes that are subject to change: the code itself, the model, and the data. Their behavior can be hard to predict, hard to test, explain and improve.</p>\n<p>There are some aspects that are important to know before you start building the system. For example:</p>\n<ol>\n<li>Will you be doing online or batch predictions?</li>\n<li>How often will you need to train the model?</li>\n<li>Will your pipeline need to be fully or partially automated?</li>\n</ol>\n  <p>&nbsp;</p>\n<h2>Use Case</h2>\n<p>For this example, we will take the use case of making vehicle recommendations for our Newsletter. The newsletter is sent out once per day and will contain a set of recommended vehicles. As additional information, we have a lot of new items added to our inventory each day. Therefore our system will have better performance if we train the model more often. Since the newsletter is daily, we can make a single prediction and have the data ready for the newsletter to use.</p>\n<p>And of course, rather than perform these steps each day, it makes sense to automate this process and save our time for more exciting challenges. With this in mind, we can start answering some questions and determine which components we need and what the general structure of the pipeline will be.</p>\n  <p>&nbsp;</p>\n<h2>Overview</h2>\n<p>Even with a described use case and defined requirements, there can be many possibilities for design structures. We are extensively using AWS as a cloud provider. So, we will use SageMaker to automate training our model, and as storage for the temporary results, an S3 Bucket. Data preprocessing is a separate step and will be implemented outside of SageMaker. The final consideration is how we will deliver the predictions to other teams.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/091f446f1b671b13b7f953faa9eb2f90/9da67/IMG_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: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 41.705069124423964%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABlElEQVQoz3WSXW7iMBSF2QfbYBWUPUw1bKKdVfEE4g1eIEhMVQZNoCGFGELCJCH/P05aesbXEa2qqg9Hjj/7nnt8lQY7hnjaOmBHH4bpY3c4f9F3nLRlnlg9rHQHe+HRMO0QSZKgyFPEUShXznPwIkPJC/mdJnHNJL8qQ1Vx4O0C4AI/iLBj/4ThKYJ3DrBYLKAoM2jaBnkuTFLRII4xmUwxGAywXC5RFFyygnMYBsOP21vc3HQwnU4RJQW2xgmNozB03DMeHn5DVVWs12tkWSbScFiWJc36/T7G4zFc15VnVVXJBp1OB81mE93uT/DygmfDrg0p4Ww2k500TZNFlNL3fQyHQ/R6PYxGI8lpPGVZQtd1tNtttFot/Lq/Q5pXwvBUzzCMEpimKZ9NKcgsSVOZkjGG+XwO27blnkZBojuKosgXPOsbnP0Yu72Y4d4KEIQxoiiSppSAklARrZ7nSh4EwTu/nlEDqnt9fYHjhXVC+l3+qAxPugVta2O1MbHSPnTl683xEyep2kHcr/njXwZ2cPAfYfNAaR92sB8AAAAASUVORK5CYII='); 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=\"IMG 1\"\n        title=\"\"\n        src=\"/static/091f446f1b671b13b7f953faa9eb2f90/40fad/IMG_1.png\"\n        srcset=\"/static/091f446f1b671b13b7f953faa9eb2f90/707e9/IMG_1.png 148w,\n/static/091f446f1b671b13b7f953faa9eb2f90/649e0/IMG_1.png 295w,\n/static/091f446f1b671b13b7f953faa9eb2f90/40fad/IMG_1.png 590w,\n/static/091f446f1b671b13b7f953faa9eb2f90/9da67/IMG_1.png 868w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n <figcaption align = \"center\"><sub><b><em>Main components</b></figcaption>\n<figcaption align = \"center\"><sub>source: draw.io</em></figcaption>\n<br>\n<h2>Pipeline Components</h2>\n<p>Let's describe each component in more detail, starting with the model. Ideally we would want the model to continue training on new data, rather than from scratch each time. This continual learning will allow us to update the model with less data. Instead of loading data from the database for the last few months, it will only need to fetch data from the previous day. This incremental approach of training the model will need much less compute power. It will also help us to avoid storing duplicate data, as each model update is trained using only fresh data.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/e0b32c3e2f366966fab1c9826ca5beb8/86db2/IMG_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: 38.61967694566813%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAABl0lEQVQoz01Sy3LaQBDk//8k5xySSlJ24oCrTJUJYIEFSBFgad+rB7K0yD50ZjdQyaFrZnpne2daGuXM4JBrMFlhf5RgosJLYXAkjssahxeFnFsUogznnvNnvoerhlBD6gbHwkKZFqM02WI6fYC1BpPJGKzIsdtt8Wv2CGs0Fos5NpsYUgqMxz9hiIuip8C7voNzPc6EqmpIuCbBNCHBKRElCU7AGUOaJCQ4I67CjGIce0EZBLXWWK0iLOZzNM0JbfuK89mhDIINRt/v1/h0s8TXu4iwwpcfEeEJn2+X+DZe42YS4/Z+E3DNfbx72OLDx0c87wpoJXA45tCWVhZSgQvyqeBI0ixEXzP+lzO2gpDkJ9XG1sRLSGVo0jJEP+Fr20Ibqg1NONC4zjmcTg2E4NTQXnxxtFKDLPsNpWRY2efeBs4Z9lkW7nRdh77vYcvLysPgiOiCD2/DEAx21HAV9Y/0dKltT0Ho/f0NdV2FjzQM59Drh6rJT+UnjHc5rnj+L79ik7JLXoQ89CSUE/71FFiu9/R7lfgDorNKPr2LIv4AAAAASUVORK5CYII='); 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=\"IMG 2\"\n        title=\"\"\n        src=\"/static/e0b32c3e2f366966fab1c9826ca5beb8/40fad/IMG_2.png\"\n        srcset=\"/static/e0b32c3e2f366966fab1c9826ca5beb8/707e9/IMG_2.png 148w,\n/static/e0b32c3e2f366966fab1c9826ca5beb8/649e0/IMG_2.png 295w,\n/static/e0b32c3e2f366966fab1c9826ca5beb8/40fad/IMG_2.png 590w,\n/static/e0b32c3e2f366966fab1c9826ca5beb8/b3fef/IMG_2.png 885w,\n/static/e0b32c3e2f366966fab1c9826ca5beb8/301c0/IMG_2.png 1180w,\n/static/e0b32c3e2f366966fab1c9826ca5beb8/86db2/IMG_2.png 1362w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<figcaption align = \"center\"><b><em><sub>Incremental training</b></figcaption>\n<figcaption align = \"center\"><sub>source: draw.io</em></figcaption>\n<br>\n<p>We are using SageMaker for training. We constructed it by loading a SageMaker Pytorch image, optimized for GPU and customized with our training configuration. During deployment of a new version, the image is pushed to ECR so that SageMaker can fetch the proper image and then spin it up inside our SageMaker space. After training SageMaker saves the resulting artifacts to an S3 bucket. But Sagemaker won’t include the ETL part and we need to put in place the data loading and processing part separately. For this purpose we will have a dedicated job in Glue. It’s a serverless data integration service for performing ETL workloads without managing any underlying servers.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/ea172fd544b3e1400c1ee9ab71e6a3f3/1ce75/IMG_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: 25%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAFCAYAAABFA8wzAAAACXBIWXMAAAsSAAALEgHS3X78AAABSUlEQVQY0zWQy0rDUBCG+xI+hwsfwYX6DL6BCxHcihtFQRDcKLgQpCIoIgiKItVqFRGxVitqL1DbNElzcnJi0zbUxi4+j6EuPoZ/Lj8zkzDsL6RqI2QQR1e1cKXGG6B1nB9o4QUx//q/JnVfqSJIKL+DCgSZlxQNVcUshByt+qR3Jf1en/TbEUunU9wXU0TdiGT+joXbE0qOTbMpWL+aZ+tmmaCtqBryzzDElDX20zsU63mergSzY1lWp5/ohRFrJ4sMzw2RvNykE3SZOdxmdGOR2493qvUyEysjTK6P43gNbeiRiNeVDvlCDse1UW6L9PErLw+f9KOIXOGRw+s9XkvP/PQiMvksB5mUNpD0oi4Xj2dcZs/5/g6p1PTJhqWo1V3MQbQcD0cpHOljmBLL9pFuGNcNSyJEE6mxbIWpcYX+odvWcz7lSoNfiJ9eulb9R3MAAAAASUVORK5CYII='); 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=\"IMG 3\"\n        title=\"\"\n        src=\"/static/ea172fd544b3e1400c1ee9ab71e6a3f3/40fad/IMG_3.png\"\n        srcset=\"/static/ea172fd544b3e1400c1ee9ab71e6a3f3/707e9/IMG_3.png 148w,\n/static/ea172fd544b3e1400c1ee9ab71e6a3f3/649e0/IMG_3.png 295w,\n/static/ea172fd544b3e1400c1ee9ab71e6a3f3/40fad/IMG_3.png 590w,\n/static/ea172fd544b3e1400c1ee9ab71e6a3f3/b3fef/IMG_3.png 885w,\n/static/ea172fd544b3e1400c1ee9ab71e6a3f3/301c0/IMG_3.png 1180w,\n/static/ea172fd544b3e1400c1ee9ab71e6a3f3/1ce75/IMG_3.png 1456w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<figcaption align = \"center\"><b><em><sub>Pipeline</em></b></figcaption>\n<figcaption align = \"center\"><em><sub>source: draw.io https://aws.amazon.com/architecture/icons/</em></figcaption>\n<br>\n<p>Individual AWS services are powerful tools by themselves, but chaining their outputs to the inputs of others services can be challenging without step functions. Step Functions coordinate different pieces of the pipeline and control which steps execute and when. It has a json format that represents a structure. States is an object containing a set of state/steps. The Step Functions service (SF) coordinates the order of execution.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f23cebe966e6a2c802df6140a532ee19/6d785/IMG_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: 59.78260869565217%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAABYlAAAWJQFJUiTwAAABZUlEQVQoz5VT2W6DMBDk/z8wT80hIKQBDMY24AMy3XUCIkqrpJZWrC00nmOdWGvhvQd/Q/BwXmO0HabJg9ftdov16UqmacLpdIqltYYZaqj+Ch/sCvauthcnvGGgLMvQthLWDcQu4D9rqyBZmv3+C3l+Ri3OqJoDdF8R2yqyHWwbv7ovocw3WpVSf419CO6VITdlWSJNU1SVQD8oDGNLXkr0Y0O+GrJgRJgcAZDnYaDexrN5np8BF7ose7fb4Xg4QnYCjcwfrK5kg/5c8rLhm5RSuFwKFEUBYwyxE5AqjxJblRFr+cTm11C2gM451DWlrHSUxIl3pkCnC5LdbwD+HqXVw67j2WM/ZgJ3D3+mF2nvKgLyUAtRE0NPicqYZmcuccD/uyIg+1VVVSwhGgrBxpcyzT4myj0ztpQ2WxGo53M+YyuWnhWtkhdATptZMsP73N2Z8swJeaSREqsCngKp8/jP8rp+ANvEqYNckCZwAAAAAElFTkSuQmCC'); 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=\"IMG 4\"\n        title=\"\"\n        src=\"/static/f23cebe966e6a2c802df6140a532ee19/40fad/IMG_4.png\"\n        srcset=\"/static/f23cebe966e6a2c802df6140a532ee19/707e9/IMG_4.png 148w,\n/static/f23cebe966e6a2c802df6140a532ee19/649e0/IMG_4.png 295w,\n/static/f23cebe966e6a2c802df6140a532ee19/40fad/IMG_4.png 590w,\n/static/f23cebe966e6a2c802df6140a532ee19/b3fef/IMG_4.png 885w,\n/static/f23cebe966e6a2c802df6140a532ee19/301c0/IMG_4.png 1180w,\n/static/f23cebe966e6a2c802df6140a532ee19/6d785/IMG_4.png 1288w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<figcaption align = \"center\"><sub><b><em>Example state machine code</em></b></figcaption>\n<br>\n<p>By default SF sends the output of a previous state as the input of the following state. It provides service integration, which means it can connect and use other AWS services as steps. During execution there is an available context object, which contains info about the state machine and execution. This allows the workflows access to information about a specific execution. There is also an execution log, where you can find info about tasks and time spent in each step. This can be helpful for debugging or tuning your execution.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/002e2cabf19bf7c3dd2b5c1d138f1f43/7acd2/IMG_5.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: 85.11627906976744%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAABYlAAAWJQFJUiTwAAADgklEQVQ4y4VUS28cRRDeP8GNf8CVExdunBBIcEICBBfuPA4B5RCJCxJBkXgksYIiHkLYkbBIFFCw1yRRhOxdO2ucZf3cZB8zO7szPdMz/ZiemZ3YHzW9m0iRgjiUuqu76+uvq77q2oQJpOkxeJyCJxoRVwi5RBiRqRQsz8GEBgsTGkswBTBOfiSeMB4rDNwQNRYKrLevI+AuWtttuK4LFgTweYhg5xDi2gZk14EiYHnwB9TfP0CFDqROIaWAkhJCCBijMRoT4MD18Panz2Fl63swP0HAAkRBiCjXcC5eR+/5D8GWbiMDMbv0GvzTz0DebyCdnkArRa8jYBoVmQV0vAl+qV9Ao7OKVBtore2t2cMC/bVNbJxawKjxD3KcYPDnEvZ+/gxi3Eea5XR2DiiVBbeAYz9C7/4QzsCz1DU9RUlN4CnlUMCRHHGqbUDX9bHTpZTwhPa1ZaWVJEBJcwnXY6jxRKGcKhS5QiIkDD01KwiAbp6aEmVaUhAF0wWGAnWcwMxZKTOFKkBxRKDI4VGBa31X4OJSgis3OMKQY/3XGKvfxvCcEIubX2OxcQGCkp+lBlf3WviquYZh6KMoHkI2F6CuvQfudMC6ffTa+6jtHXG8+tYu3j+9D99nOP/RAc68vovOzgBvLLyIN8+/hIk/QcRCfHz1J7x8+SxavSMcnwDe5XfgnHoW7MFdNL/4EuvfXELNHUW4desIzc0+MZFo3DnE2m9ty3Ztq46Vxgo455gWBW5vt7BYXwWLOXLy/cN76G3UIWk/imP0Kce1iERclpSfTCFOBGLBKekBJVnDaKqkNCT2mKppkFBQSLKqKlvtJ4r2aD8RyqrDG0eoVV0x05K2Va4CK6sOaKpulT9OQFma47s75/DJ8rs4Gu0iNwWSJLaXKKvDqsrhDNCY1C4KMQOvwNJUz5lQK0YRJLE4c+UDvPL5CySdu8Q8JWaJJVGxfSzs/wKcgc66oGKRkWzGwQTdwYOZ/mSlV/V/gOIJwMoyY7A36OHmvRb1r8ZxeYwRG6J58Bd8NoFIxPzJjwDpVzHp0wBpjcYyL1Bvb+Ps78twiGGZT3HgdrDc/BF9r2fb9VEv2xxG9O3kuZn35OzJs/zNGOaZwdAbYXO3Yy/Mswx+OMH2/pZVREZ+FWfovDeJqt8mtD3okA2cyVNt6PhwRwz9x35A31xIc9/61XqFsd8d4V/seNz4IariWwAAAABJRU5ErkJggg=='); 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=\"IMG 5\"\n        title=\"\"\n        src=\"/static/002e2cabf19bf7c3dd2b5c1d138f1f43/40fad/IMG_5.png\"\n        srcset=\"/static/002e2cabf19bf7c3dd2b5c1d138f1f43/707e9/IMG_5.png 148w,\n/static/002e2cabf19bf7c3dd2b5c1d138f1f43/649e0/IMG_5.png 295w,\n/static/002e2cabf19bf7c3dd2b5c1d138f1f43/40fad/IMG_5.png 590w,\n/static/002e2cabf19bf7c3dd2b5c1d138f1f43/b3fef/IMG_5.png 885w,\n/static/002e2cabf19bf7c3dd2b5c1d138f1f43/301c0/IMG_5.png 1180w,\n/static/002e2cabf19bf7c3dd2b5c1d138f1f43/7acd2/IMG_5.png 1290w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<figcaption align = \"center\"><b><em><sub>Final architecture</em></b></figcaption>\n<figcaption align = \"center\"><em><sub>source: draw.io https://aws.amazon.com/architecture/icons/</em></figcaption>\n<br>\n<p>This is how all these services were brought together to implement the pipeline using AWS services. We are extracting data from the Data Warehouse to Glue jobs where the preprocessing happens. It then stores intermediate results (as a least effort solution) to S3. The Training job starts as the next step after the Glue job finishes. Keep in mind that the name should be unique within the region and account and it is a required field. In the Step Functions we also specify the S3 location for the output artifacts.</p>\n<p>The next step is to set up a schedule. For this purpose we created a Cloudwatch event that triggers a state machine and executes the pipeline.</p>\n<p>The last step in the pipeline is to load output data from S3 to a database that will be accessible through an API. This task is solved using Lambda. It is another AWS service that runs code based on the incoming request or event. In our case Lambda populates predictions from S3 to Redis. Redis is an in-memory structure store that not only supports a caching solution but also has the ability to function as a non-relational database. It will override any specified fields that already exist in the hash, or if it’s new, create it. It works well for our use case. Finally, we created an endpoint using FastAPI that fetches data from Redis and provides it to the other teams.</p>\n<p>To create all these services manually and deploy them would be time consuming and difficult. Instead we are using Terraform. It is software that creates infrastructure and allows you provision resources quickly and consistently and manage them throughout their lifecycle. By treating infrastructure as code we can avoid costly mistakes that can come from manual setup of services.</p>\n<p>Here is an example of Terraform instructions that manages schedules in CloudWatch and triggers a Step Functions.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f02efe920295efbeee767de440838fb9/35075/IMG_6.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: 39.00481540930979%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABYlAAAWJQFJUiTwAAABW0lEQVQoz51R23KDIBTM/39gO+mYxAtKvKCgiFzE7ZFk2qe+lJmdPUdhz7JcGKuxLBLj/MAgM7TjJ/Ed/ZhByJzqB+RSoxVX4gbL2mOaK0INpTm0GeC9x3EcCZc8z9F1HWp+Q1Fd8Ww5pOqT0InzsNJPCFXAugV7DPhrJcGiKFCWBfp+wDD0JPoF3hbkZKDpAmaTWLcxOVMLp35KOL9tboaxMrkMwSbRi7UWzrmEczm/wBKcX7HZma5jElunk0MfDP3XSciH7V2rH+cXrTUdMEnohLEjbVxp44wQN3IxIUYaGBaq5WugOweoNNgmlxP2/S3IGCO7HnqVqJscrLmjKDNUjLi6oazooTqe+uz+QRkz6hlKllFdoRtqyv8BpdRLkHPKxRisq0HTcNR1g6piEIOAEAJKzpBSYRxHwkQRBHJ8kKMj8RFfHGP8zfDEvu/kNFCWlq7s8Z91vvI3DgJm2foWbQYAAAAASUVORK5CYII='); 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=\"IMG 6\"\n        title=\"\"\n        src=\"/static/f02efe920295efbeee767de440838fb9/40fad/IMG_6.png\"\n        srcset=\"/static/f02efe920295efbeee767de440838fb9/707e9/IMG_6.png 148w,\n/static/f02efe920295efbeee767de440838fb9/649e0/IMG_6.png 295w,\n/static/f02efe920295efbeee767de440838fb9/40fad/IMG_6.png 590w,\n/static/f02efe920295efbeee767de440838fb9/b3fef/IMG_6.png 885w,\n/static/f02efe920295efbeee767de440838fb9/301c0/IMG_6.png 1180w,\n/static/f02efe920295efbeee767de440838fb9/35075/IMG_6.png 1246w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<figcaption align = \"center\"><sub><b><em>Example of terraform instructions\n</em></b></figcaption>\n<br>\n<p>It’s very straightforward. We need to specify an event and rule, using the rule with a cron expression.</p>\n<p>There are two steps that should be executed during deployment.</p>\n<ol>\n<li>Terraform plan: Checking the execution plan for configuration before creating/changing infrastructure.</li>\n<li>Terraform apply: apply the changes.</li>\n</ol>\n<p>With Terraform set up, we have all the parts in place for our system.</p>\n<h2>Conclusion</h2>\n<p>This is only one example of how this pipeline could have been implemented. When it comes to creating your own pipeline, you will have different choices to make. Here are some of the lessons we learnt to help you when making those choices.</p>\n<ol>\n<li>\n<p>Step functions did an excellent job of combining services. It provides a lot of flexibility for creating different scenarios and logic flows. Overall a great choice for managing your pipeline.</p>\n</li>\n<li>\n<p>It is a best practice to limit access to AWS services. In AWS it is the rule that anything not explicitly allowed is implicitly denied. Each service should get its own IAM policy to handle this. This can make it tedious to set up, but is worth it for the security and peace of mind it provides.</p>\n</li>\n<li>\n<p>On the same topic of security, you should avoid putting sensitive data into S3. If you are using Jupyter notebook, be aware that it has access to the public internet by default. You should consider if this is really needed for your use case.</p>\n</li>\n<li>\n<p>It is worth understanding the basic concepts for VPC’s. We didn’t discuss them here, but they are an essential part for running many of the services. Also some services will need to be in the same VPC to access each other.</p>\n</li>\n</ol>\n<p>I hope you find this helpful, when you start to implement your own Machine learning pipelines.</p>","fields":{"slug":"/machine-learning-pipeline/","tags":["auto1","DataScience","Data","MachineLearning","Architecture"]}}},{"node":{"id":"588b2b03-a726-5249-9d5a-975640153a62","frontmatter":{"category":"Architecture","title":"Evolution of SPA approach at AUTO1","date":"2019-04-11","summary":"Building single-page applications","thumbnail":null,"authorName":"Oleg Tudoran","authorDescription":"Software Engineer","authorAvatar":{"relativePath":"pages/spa-evolution/avatar.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAUCAQT/xAAXAQEBAQEAAAAAAAAAAAAAAAAAAgED/9oADAMBAAIQAxAAAAGtmN2aro5nj0dJA//EABwQAAIBBQEAAAAAAAAAAAAAAAABEQIDEBMhEv/aAAgBAQABBQLaK+p30kjfcPp6Z//EABcRAAMBAAAAAAAAAAAAAAAAAAAQERL/2gAIAQMBAT8Bhl//xAAXEQADAQAAAAAAAAAAAAAAAAAAEBES/9oACAECAQE/AaaUP//EABkQAAIDAQAAAAAAAAAAAAAAAAAhARAgMv/aAAgBAQAGPwIZ1GXX/8QAHRAAAgIBBQAAAAAAAAAAAAAAAREAIUEQMXGBkf/aAAgBAQABPyHgJTehCyAUh4MuNDE70MruqIpz/9oADAMBAAIAAwAAABAbNwD/xAAYEQEBAAMAAAAAAAAAAAAAAAABABFBcf/aAAgBAwEBPxACXVqy3//EABgRAQADAQAAAAAAAAAAAAAAAAABEUFR/9oACAECAQE/EJko1Tj/xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhgTHh/9oACAEBAAE/EL/XKmAfiXH40r0QHrolRSpPNF7z5hXq9cpZiC65CQeXf//Z","width":50,"height":50,"src":"/static/24ab1ea711b3329ac0f7f0d8386d0c6c/d2d31/avatar.jpg","srcSet":"/static/24ab1ea711b3329ac0f7f0d8386d0c6c/d2d31/avatar.jpg 1x,\n/static/24ab1ea711b3329ac0f7f0d8386d0c6c/0b804/avatar.jpg 1.5x,\n/static/24ab1ea711b3329ac0f7f0d8386d0c6c/753c3/avatar.jpg 2x,\n/static/24ab1ea711b3329ac0f7f0d8386d0c6c/31ca8/avatar.jpg 3x"}}},"headerImage":{"relativePath":"pages/spa-evolution/spa-winding-road-evolution.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaklEQVQozw2OZ1PaAACG86faa+/as67YskfIImQnBDNJAiEMCUuECso6AZHW0dOrrVQ5279XvrxfnnvueYGQ3CQKA8Gb6Z3v7vDBmz415qvm5aq9WHWWz/2b197Na/vqpT5bVSaPhcGD3b+vz/70b/8dL9fA0fl1d/5zfLee3/+d3K17i1/10Y9MbUK5A7o04qtTo3ML26NtrvEWL74nSjt8M2EPtZMbsb4E+MJXodivnn3rzH87Z/eCt8CcEZofJnKjsHEeUHtIbsx6C/rokvGupNZ17vyxfPEiNa6CYgXYpgogX92cJ4tD4/TOnawq05cNzg+f0rUFXRwqJ7dH0+fmYl0aPWitqd4Ya14PzegBggHCQh7SmrjdTdlt1u2KlTO9NdNaF1Su6SczPoJLSAahFyBRjVKMD0VDWAKmUJonw9EDAOIyEU4P0iqI8Qc49wVjIIaP0WwoSQZwMpxM4QwXhyEYhZNEgqJgkUdlkRRIIhYOASRPc4cyRNF+BAslUVwgERZLHzKaIpIExhKYpUhGms1KnMKmGCyuCmRek1gSTuIQEE0EOIHQVKladqQMAyEggn1S+IQixiUupkuIqeBaOinzhCmzriXbskCnDmhmi2I+Ahwb1mTEyXLLSa/u2ji+pWT85Rxlm5BhBFs1tl3n81ZEVXatrP+0XRz3yq4TVZXPDLMHSBzGUgFD8Q1PiycNK5eNVYuYZfo0bU/X90ulgOcFSyW/ae7JGVCSdlTtg5Pfr5QjbiECaGnYVJKTvrcJDzrVbtPYaDT7huXeieJHw9g1s6CugZYFmsZmtx1nx3XBWjV+XCf+A7RwItURocOQAAAAAElFTkSuQmCC","width":1200,"height":630,"src":"/static/f11171542efa1c6ff84e967a0df8a206/6e93c/spa-winding-road-evolution.png","srcSet":"/static/f11171542efa1c6ff84e967a0df8a206/6e93c/spa-winding-road-evolution.png 1x"}}}},"html":"<p>Here, at <a href=\"https://auto1-group.com\">AUTO1 Group</a>, <strong>we are growing</strong>. Not only in terms of company size, number of employees, revenues, etc, but also in terms of database size, amount of servers, lines of code in applications, amount of accessible pages. For supporting this growing codebase we are adopting new approaches and new technologies. Different innovations were already introduced, but today I want to tell You about switch of a paradigm from MVC to SPA, and how implementation of SPA approach evolved.</p>\n<h2>MVC approach</h2>\n<p>We have our internal application, called <em>Admin</em>, which is heavily used by hundreds of colleagues in many countries. Historically, it was written as a monolith php application, based on Silex (and later Symfony) frameworks (some overview is present in <a href=\"https://auto1.tech/vigolante/\">this blog article</a>). <em>Admin</em> application had architectural layers, which fell into a concept of <em>Model-View-Controller</em> (<a href=\"https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller\">MVC</a>). The <em>View</em> part, which is in essence the HTML code of all pages, was prepared on web-server using TWIG templates. Usually, one developer was working on both back-end and front-end implementations of web-page. That was called <code class=\"language-text\">Full Stack development</code>.</p>\n<p>Downside of such approach became obvious when first HTML tables with complex internal structure were implemented. Lets take as an example a page that displays hundreds of cars, provides pagination, possibility to edit data, shows aggregated results, etc. Such page consists of static components (navigational menu, list of available locales, information about current user, html-template, that renders appearance of the page, etc) and dynamic components (information about cars). This kind of web-page is <code class=\"language-text\">hard to build for a single person</code>. Knowing that development cycle of such complex pages could be split between several software engineers, we divided responsibilities between back- and front-end developers. We decided to generate the <code class=\"language-text\">View</code> part of MVC architecture in client's browser instead of application's server.</p>\n<p>And this was exactly the case, that fitted well into SPA paradigm.</p>\n<h2>SPA paradigm</h2>\n<p><em>SPA</em> stands for <em>Single-Page Applications</em> - an architectural paradigm, that divides elements of the page into static and dynamic.  Static elements of the page (such as page layout, navigational menu, user information) should be loaded into client's browser only once per user session. The page layout rarely changes.  On the contrary, dynamic components of the page can be updated continuously.</p>\n<p>Regardless of the technologies involved for rendering a page in client's browser, main difference between SPA and non-SPA approaches is whether a browser <code class=\"language-text\">requests each time full page content</code> (including static content, such as layout, menus, etc), or it <code class=\"language-text\">requests only the important (dynamic) content</code>. This difference can be seen in a diagram below.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/99908d8f36bcb222572f33850246a4f6/c9722/TraditionalVsSPAPageLifecycle.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: 111.53415453527435%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsSAAALEgHS3X78AAAFlUlEQVQ4y31UWXBTVRi+qSwvLsOouMzoqCj4Uofq+OCbM/oAo6JObWVARRFrKUgL7QBCGWjZaZmOxU5ZRkwpTFvoQk2TNEubmGbf96RZuqVpk5CS5d4kN8u9x/9WkIoO/8x3zzLnfPc75z/fj5EkWUgkk1Q4EqEyJEkVCgUqDyCzZC5HI+SZjpRiS4I08zCEEFZVffCx46ealx04dGz5JfZAUVuf/96CVIrK4DhigMdiCI/HEQHIplIF2IjuxhJ7ZwKBV7xTUyVZhNYu4V4FeAnw4v2Jsu1HWdgsjlOeVApNpNO0B8f/RjJJewicCuRyyD850y4SSY6I+cILFp2pgT/AXc1sbrl0+93Om4LK67eEFeyekQ//+Y0yHqdm0mkUS6XoyXAYeYNBFALCRDqdN4NCsd29QckRvjWv0NR6eYI6t9H6NbtfsV8kMyO5xoGUOicSyUzoxoCc/V5Z+3JMBYTORAK5QiHaOTeH7LOzyA39WZhXpNJoKhI9O8wXbenl8Cp0dtdmRgRHpFNaXAEkUztSCr07I5YZ0ZBIF62s73keU8dilDMSQeZMhs6BIubioKWZrhM+uvGJevWYeodJoW52aw2NRpW1+LZAO6S1+JFEYc2MaZykUKpHXLHO9+OhrmcwDRD64ah2kqQpIMhnsyibzdKIpvOTMJb7pzdw2d1rp6SKXeOD3J1qiXpLH1/D15h8aFRuJYAwzR/RFjp65dOfVV17FtMmEtQc3KENCPM0jZLJJIolEjSiKMoOY+9c+FcBX/jtjcGh79Vu7+ITGhRoxxy+MBzZiXSgtH9Yhy53K1Ere+QqJkulcpcKhbyEovKMwCXIuhmFTs8uhUS+1aUxtDrl6iaz1rJxUKg9CcmQSZVWnkrv5P5+a0zyC3sENV0WdGJ22NQBUABSgCQgca8NAhyBuc1dbVfXjENibD395VqJfCP2P1FW0/Uma+3PKzDnXKhBG4o0qubDjZJgqFG6iPnG0flwgzY4f9bnmVz/8GZEzrJQxs+yaTisaMBSxDjngZUIojoeDtekY/EaRGZrUC5Xg/KFGpog9twJR/db/aF1zDqfwbwyYrIsj3gnih7+AQmEFfU3iyoOd2OMLx8Zgfno5n+pg82lledYr75Rsurl14pXr1n39tPNbT3Lmtka1uICgiBycRwvJAiisBCPLyIai+VjeDIHiUaRSLRJodJ8PqpQfeUNhj5awv0E4DnAk/cnGs53sTALVBobeNkHXnbE47Tt7l3aA31vOl0wgZet4/4jUrG0Qi8abXHKlCd8JvM7GHZhBfuWrHZQoO65Pazq7uyVNpVWg0uYUIDFmHcYj8XoKfCxf3oazUejcIVE3kTRSDcd/FjSzy32yuRVDoFwp0Ki+LSHo7pucc8hpd6NtGYfUho8qI+nNpXt7nh80cvGaBTpQFkAlAagjM1AcbiTJSl9lkLiCdWVet7RapXIXKuW27YzIobEOqnJMYXGNI6USu9Oi2UGCrw8t/NIzwuYBgh9QGgFu9EPcrHoZR98lA7/ttbe9k3N0taW46Iz51rEV8q5IgOPIZSpbRmFzpWVyI1IINH7v9nX8RSmBcLgwgJyQHFgCkOKIBBOEOBlKu+FsTu8sGXvxT3rqnl1pTs4lZvK26tKOALdMHPUP1U2Qql3pTkibe78b+JI5bGB9ZgqmSyowcd6KPhMZCAhBNRGxssuqBbDPuXl06IzdbYR12G7zL2LOfIfIu3Y+GQUKXRuZLRNol6+Ad0ctqO2zhE5JiVJdAKUDP/3CdLMkTXO6S/3Xa374AdB1bmtQ9tOnpKd3943oP+EJzHUcoS63QKpsar9uuTg6YsC7ck27k+YMRaP9eNEQojjcTW0KhxPqJJ4XIUTC3LI/MRU8Avs/ZWrvuurLNnUVV6859qB17FHxF9D2cBydMXFhQAAAABJRU5ErkJggg=='); 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=\"Traditional vs SPA\"\n        title=\"Traditional Page Lifecycle vs SPA Lifecycle\"\n        src=\"/static/99908d8f36bcb222572f33850246a4f6/40fad/TraditionalVsSPAPageLifecycle.png\"\n        srcset=\"/static/99908d8f36bcb222572f33850246a4f6/707e9/TraditionalVsSPAPageLifecycle.png 148w,\n/static/99908d8f36bcb222572f33850246a4f6/649e0/TraditionalVsSPAPageLifecycle.png 295w,\n/static/99908d8f36bcb222572f33850246a4f6/40fad/TraditionalVsSPAPageLifecycle.png 590w,\n/static/99908d8f36bcb222572f33850246a4f6/b3fef/TraditionalVsSPAPageLifecycle.png 885w,\n/static/99908d8f36bcb222572f33850246a4f6/c9722/TraditionalVsSPAPageLifecycle.png 893w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>First picture represents traditional request where full page contents are passed from web-server to a browser on each request. Second picture illustrates SPA approach: full page content is fetched only once, afterwords only dynamic content is downloaded and full page reload does not happen.</p>\n<h2>SPA evolution in AUTO1</h2>\n<p>Starting from a monolith application, where HTML was rendered on a server-side, we gradually moved to our current state, where a separate application serves SPA for us. But let's go step by step.</p>\n<h3>From Monolith to php-rendered SPA</h3>\n<p>Adopting of new front-end technologies, such as JavaScript library <a href=\"https://reactjs.org/\">React</a>, allowed us to start building first SPA pages. React code is executed in the web-browser. Front-end (React) developers became responsible for all the visual representation of a web-page.</p>\n<p>For organic integration with new technology we updated existing workflow of generating HTML of the page. Specifically, we introduced several new endpoints, that provided essential information for rendering HTML page layout. Those endpoints were:</p>\n<ul>\n<li>GET /menu - returned JSON-encoded structure for rendering navigational menu</li>\n<li>GET /locales - returned JSON-encoded list of available language and country settings</li>\n<li>GET /user/{id} - returned JSON-encoded data about current user</li>\n</ul>\n<p>Front-end developers wrote <a href=\"https://reactjs.org/\">React</a> code, which could fetch data from aforementioned endpoints and render HTML5-compliant page layout.</p>\n<p>Now we needed a way to pass this React code from web-server to client's browser during initial page load!</p>\n<p>This was achieved by introducing of what we call SPA-controller: set of php application's code (written with <a href=\"https://symfony.com/\">Symfony</a> framework), which returned very basic standard html template that contained latest version of the React code. Being executed in the browser, this code rendered HTML contents of specific SPA page.</p>\n<p><code class=\"language-text\">Under the hood</code> of SPA controller we implemented such functionality:</p>\n<ul>\n<li>based on request URL, define exactly which SPA page layout was requested</li>\n<li>check if we already have this HTML-template cached</li>\n<li>\n<p>(if template was not cached, or TTL of the cache is over) do a request to CDN, that contains actual version of front-end assets</p>\n<ul>\n<li>put new HTML-template into cache</li>\n</ul>\n</li>\n<li>return HTML-template of SPA index page to a requesting browser</li>\n</ul>\n<p>The response contains lightweight HTML similar to this:</p>\n<div class=\"gatsby-highlight\" data-language=\"html\"><pre class=\"language-html\"><code class=\"language-html\"><span class=\"token doctype\"><span class=\"token punctuation\">&lt;!</span><span class=\"token doctype-tag\">doctype</span> <span class=\"token name\">html</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>html</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>head</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>meta</span> <span class=\"token attr-name\">charset</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>utf-8<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>meta</span> <span class=\"token attr-name\">http-equiv</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>x-ua-compatible<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">content</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>ie=edge<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>meta</span> <span class=\"token attr-name\">name</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>viewport<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">content</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>width=device-width,initial-scale=1,shrink-to-fit=no<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>title</span><span class=\"token punctuation\">></span></span>SPA index<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>title</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>link</span> <span class=\"token attr-name\">href</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://cdn.xyz.de/1.2.3/main.css<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">rel</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>stylesheet<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>head</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>body</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>noscript</span><span class=\"token punctuation\">></span></span>You need to enable JavaScript to run this app.<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>noscript</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>div</span> <span class=\"token attr-name\">id</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>app<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>div</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>script</span> <span class=\"token attr-name\">type</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>text/javascript<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">src</span><span class=\"token attr-value\"><span class=\"token punctuation attr-equals\">=</span><span class=\"token punctuation\">\"</span>https://cdn.xyz.de/1.2.3/main.js<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span><span class=\"token script\"></span><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>script</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>body</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>html</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>As You can see, SPA index page contains none of the data that should be shown on a page. This data will come as a result of subsequent requests.\nStages of building SPA page are illustrated in the diagram (notice 3 phases):</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/e5d2e836bb8bf89ae91ffab228f6e013/d9801/3phasesOfSPAPage.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: 132.49566724436744%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsSAAALEgHS3X78AAAGI0lEQVRIx41VaVBTVxSOYLVKdVRAQTu2g+DS0TraaZUfbSnj6NipdaYzHevWTrHjglqVcbe41a0ILggoEhSs7BAgbCGELIQskLBEdhIIECALIeFlhbzl9l4WI9Xp9Mx8c959957v3XPOd++j2ex24BwdBVabDWBWKxjBMBJAM5uxczRoY2NOT+SDVm+cA53nJLzgkhm0d5nQ4SDTcJzKnkQmQbgq4Oo6neEsmmdIZLOm1v6w68Dnu/cfDtm4eftSNE54FD3L6iC2ECTYAUO+czhHQ2klo6PkAzhKAICKh/4RAHgW9FKj6dQU0ewVeTNyS2sTyrhyopwnB4xSiepKTHZIxN513vVKh8NkA+OGWWzDNBYkjIODpxRFQYDHFEUgQqGyKzUnI+unXGbRr388yFhdIXw1IKnrBAJpi10kV4L0gurr6GP6IXOsacT6zGq1JRuGTFH/JqQgIZ4DxzyV+kb1nfu7Gv9Ov8BNSj/EFTcpxbJWQlTbYuFWK8DzLO5xyDdrcFCrhXDpdPox47BZiVLGYcokTJmEKZOxAIxlQEKxfuiMIPysTwe7Yk0anRFcUaUwK9o0oLZBCQoqFCCGXlF/+W72PL3B+IW6dyCkVzP4tbKrbxOtFgbnQhROAj1XTzTl0uvObX02s4xbd58rUqTxRIqkJy+5nHt0dlNkdL73W13u7OoJbelQhbYqu0PbIKD/pl2l3qpo7lyO5rNTMj1p/2FtKq2nXm/00EGoewY8aE6nU2CzWqsIHB8HSeA8h9NZr9GZ9qAApLd5C5fNXbJs1XJf/6Ctvv4rdyz2Dwzw8lowb4o0q0js/gKYNKRmAsLpck1IYMRy4Y2NvOcx08sP+rlI1BC+ASs/m48mfJYE0DZtO+9e2WSx4N0OB9FlNhNtQ0NEn83m6iZw0DCoS2IXle4SiiTHIL8PjRb8/vkr9w9G3oj9/cjpmA0o9vbtOx5v1UA2MkIqTSbQrNdT9f39AD7jSsjAVvddZ+WXHGAWl8WyONUnSyrrxFLYYbG8HRSxaxzPsgTfovi0/GqPc3eL3YSNJhNZYzSCRKjBJCRskhwXtqxf+zQ/M28Pm8M7lJbHCy0XNOqqpK0ET9xsgQAZhaIIFJ9TLPGsEQnchO1whx0YhgRNJcLjB/U4Lmxhr+YmM7fwUC6j8CGzrOoiT9TUL5a34mJZi62cXw+SMvgHUfxLhnDmtJRFsH4FBEE+hn15MiFuPG1ih8nFDOZxkUR6uaBMvJMjVGCv2vuBrFEF8soVIDqJ3RN5r/CD8Vo+LnPfPHyCAHRIgE5HOkQqBeuHxN2jucYp5fzILGbd5PAk4UUV8iMsfkNEOb8hPP5F5cO7T1mpp/9Mn404bsYVuXfYpzPs7TEY96l0hn0q/RDCXvgc1tSh/oT2Pyz6uXj6C6fdzrFjGId0uTgUAu5iOx0Oab/evGtSpx5bt++ctXptsLePX2Cwt1/QVx+tWO+7dv3mOVMccamV04UNswQ4BJK0HcfHhW02YZGT855+H66a6+MfFOC9+OOA+QuXBi1eujJw4ZKgRWh+0ZJA2vdhj94QNobhXTYb0Tk8TDTrdEQ3ho32osvBYDz5jgx9IZa+vjN2/vb2b6DOYiE1FgvoGxqiurVaYMQwYgDJRqu/28uoCeiT6zYAM5hLz+TuLyitkkAJydIYvPirsSzviXQ500kbzGZSBk+KliSpUXSWobCHoa/qGYh/lBMb8YAdHxNdTI9g8RtHZK96QFVNGxDKVOBFrvAEis9kij2jElluwlZIqDKbgQkR2mwUbAw+hnY4aLh6JD5sW0TJmbAz2bfCBJImfW19u0sib7UKJAqQzRSNCzu7SDxd2FV2O8HEcXIEihtQFKnu6HDBegKp1kBPSU8+ksbOOn/rBX1HOV+hFck7AV/cTBaU14F7yRXxKD63RDrjr8RyNyG6ndHJQHWzo2ZoNEAFvcJsu7Qubs2XISVbfv4l+/gxvuhVg7SurQvusIWeybfeS2bbL0YxFo6flIQSdx2b1X3hdeq+owK15mhlV+9RkdYQXjugi6hVtG58M5OLUTnzrt3PX3DjYZ7XqZsFQYcv53yK3qcwFbTdJ1Jer/sHgu6DalJSLToAAAAASUVORK5CYII='); 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 phases of SPA page load\"\n        title=\"3 phases of rendering SPA page\"\n        src=\"/static/e5d2e836bb8bf89ae91ffab228f6e013/40fad/3phasesOfSPAPage.png\"\n        srcset=\"/static/e5d2e836bb8bf89ae91ffab228f6e013/707e9/3phasesOfSPAPage.png 148w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/649e0/3phasesOfSPAPage.png 295w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/40fad/3phasesOfSPAPage.png 590w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/b3fef/3phasesOfSPAPage.png 885w,\n/static/e5d2e836bb8bf89ae91ffab228f6e013/d9801/3phasesOfSPAPage.png 1154w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Page lifecycle consists of 3 phases:</p>\n<ul>\n<li>fetch SPA index and render empty HTML container</li>\n<li>fetch data about static page components and render a layout of the page</li>\n<li>fetch main content of the page and build it into existing layout</li>\n</ul>\n<p>This approach, regardless of its increased amount of requests, actually resulted in a faster page load times, mostly because caching of SPA index was introduced and because web-server did not render HTML code anymore (this responsibility was moved to the React application code, that was executed on the client-side in web-browser).</p>\n<p>Overtime we noticed drawbacks with this approach: actual SPA version was hardcoded in a config file in php code and full php application required re-deploy each time, when a new version of React code was introduced. Deployment of a big monolith application to production environment was slow, also release notes were meaningless and annoying. </p>\n<h3>Moving SPA index outside of monolith</h3>\n<p>Having understood that keeping a reference to a latest SPA version in the code of main application was causing real problems, we started thinking about another possibilities.</p>\n<p>The most promising was idea to create a standalone application, whose single responsibility would be to serve latest version of SPA index page to authorized users. Expected benefits were:</p>\n<ul>\n<li>decreased time of delivering information about new SPA version to a production environment</li>\n<li>decreasing time of serving SPA index page</li>\n<li>utilize web-browser's capabilities, such as <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching\">HTTP caching</a></li>\n</ul>\n<p>As a result, we implemented a <a href=\"https://golang.org/\">Golang</a> application, that did exactly the same, as it's predecessor-monolith, but outside of php environment. The current page lifecycle started to look like this:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a4e563c6b73722bb48a1f8147604cacc/efe7c/SPAFromStandaloneApp.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: 41.98606271777003%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsSAAALEgHS3X78AAACGElEQVQoz3WQT0gUcRTH38zOzra6IB6yMEFiD1md6hKEh7x1CLx08BDYP4SKwKUy1PLQpZYOZYRZUMGiJWWHLNxKKCwiSFwUYmVLV2vTdWdmd2ZnZ5zZmdnfr/ezgoh68Pj+3nu/3+f33gPTNL2SrpOybVdsy6qgOhRtRVIvAFpWUgSmsHm3iIcNfgARasJVx4928PAv00slYlgWVXWdFjSNFopFlwFVrdj7570agGph486wv7E5XOsP1LMcXmuktNKG2op+0DDWWuAtdhXzPDriuuSh55Fhz3PHsZqQlCvKxGzd91m1CcMAROZrfd1zR/juZGd99Ns2Bvz0eaVHx6LnrvdAbbucgnEctR+DW5SSAdSbWB9C/ZiVB1+9i0fuTT7oX0xPNsPpmcN818wz/sz0qeD5xDEGTM1nDi3LdtowzClzzUoqeW0EXiAQIfQOIeQ2IQzsPsb4fSZ3qTN2Ys+Bsbb2l9N390IkuZ87l7jMn01EQ11THQy4sPj1pKbmbUmSMrqua6ZpTfwGkl9AgkBnlAFz0sW/9+2LmnVi75dd7WNOgMVzqYXW7Kr0OicpT/IFNS7L6nWIl8v0BgIGf469PvIj1A9yvo89GtCfixhyPFQF/eDbLgJsxXQA/mdvsM/7rmsPO449hB5zXOMp2+Gq1MPqo+klH1MhuKlJqN6yTwg1tIihhh0sF716jdPUHIdb4tinzH8AY3NsacwXjCQAAAAASUVORK5CYII='); 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=\"SPA index served by standalone application\"\n        title=\"SPA page is served from standalone application\"\n        src=\"/static/a4e563c6b73722bb48a1f8147604cacc/40fad/SPAFromStandaloneApp.png\"\n        srcset=\"/static/a4e563c6b73722bb48a1f8147604cacc/707e9/SPAFromStandaloneApp.png 148w,\n/static/a4e563c6b73722bb48a1f8147604cacc/649e0/SPAFromStandaloneApp.png 295w,\n/static/a4e563c6b73722bb48a1f8147604cacc/40fad/SPAFromStandaloneApp.png 590w,\n/static/a4e563c6b73722bb48a1f8147604cacc/b3fef/SPAFromStandaloneApp.png 885w,\n/static/a4e563c6b73722bb48a1f8147604cacc/efe7c/SPAFromStandaloneApp.png 1148w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Now, initial request to fetch SPA index was re-routed to a standalone application, that served the same HTML, as we have seen above.\nAll other phases of SPA page lifecycle did not change.</p>\n<h2>Current state of rendering SPA pages</h2>\n<p>Implementing of a separate application, that serves SPA index pages, brought us several significant improvements in a page load lifecycle:</p>\n<ul>\n<li>SPA index page is returned significantly faster - <em>80-90 milliseconds instead of 1200-1500</em> milliseconds in old world</li>\n<li>time to deploy to production environment decreased almost 10 times (and now it is <em>under 4 minutes</em>)</li>\n<li>moving of configuration parameter outside of monolith allowed our front-end engineers to be able to <em>update version of SPA index directly</em>, without help of back-end guys</li>\n<li>additional configuration improvements allowed us to <em>gradually release feature</em> (allow switching to a new way of fetching SPA index one page by one) without a risk of braking full application</li>\n<li>automated support of <em>multiple development environments</em> was introduced ot of the box</li>\n<li>fine tuning of HTTP caching parameters allowed us <em>to serve less requests</em>, because of browser's caching possibilities</li>\n<li>In the first hour of work with this application on production environment <em>75% of all requests were served by HTTP cache</em>, with most requests not even reaching application</li>\n<li>other teams have <em>successfully adopted technology</em> and were able to release application themselves</li>\n</ul>\n<p>This is pretty good, huh?</p>\n<p>Of course, there are challenges in current approach also. We are investigating the best way to deal with user locales in SPA index, we learn how to do proper caching and authenticating user, but this is a topic of another story!</p>\n<h2>AUTO1 Group</h2>\n<h2>#spa #monolith #mvc #engineering</h2>","fields":{"slug":"/spa-evolution/","tags":["auto1","SPA","monolith","MVC"]}}},{"node":{"id":"3e19b9f8-fd63-5270-9ae1-29f538ecd8a9","frontmatter":{"category":"Architecture","title":"Migration to Microservice architecture","date":"2019-02-12","summary":null,"thumbnail":null,"authorName":"Alexander Egurtsov","authorDescription":"Team Lead Software Engineer","authorAvatar":{"relativePath":"pages/migration-to-microservices/avatar-big.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAD5UlEQVQ4y0WSy28bVRTG5z9gi8SSv4AFCySEkNggFqgVi4K6QSoqqgQBsUkfIJUFFKqKJiWgWrRNG/pQHk3TlpTUcgKOg+14HHvssed5Pe/XnRm/YztjxzFnbBWOPl2dxfzO9917htA0S9dtQ3f+k65ZluU7uK7IWkWQTBM7TlXgJY5F5RJfKDD5HE1m8qkkSZiOZzm+hUHV8LQ9168jXlyY+WH+4tQXH7534vj7d+YXTMMVBRlGsIxYojmKorPZPIH9uuc3vCoImppfbRimPXvus1PvvDb9wdvTx996/ZWX3nj15cjcHMY1JMowYhyBKxRKRL3RAjWaoWq1xn6vn46tXznz0fz5zzfnvkMbi2b8qfB4/upXn0gVRVMtSdKQqEx4otXutPe7EzWa7f5wtBSZvXz6RCcTG/n6UbcBGtnC+k8X6AKFsa8quiLrCMk8LxKd7kG3F4Cgabc7o9Fo5f69T999k7xxZe9+xE/H9M01Zimy/OM5UUSuWzUMS9NMWVYRkoiDoB+MBU2vd3A0GlHxv2bOnNyavRj/5bK+dtuOrVH3fmXXH/iuj13ftrFp2ppmSJJKDF5UfzAIgmAwHDZ8/9rUKeX54kgtC4uRQNgbOaKWitWabXhRx4WNYPBXVZ04HA5DjWswOAyCA0hOJuKPLk17WytPvvmysnyzvvPY5grwmNXxRhzsWZaj6yZxdHQ0fFFjfgCz9tutmbNTOz9/Ld262tp8yD6K2KbR7vRq9aZfrbte1XZc2GgIT2rCAwy971W/nzr97cfHCkvXiyvXEw9/b3WCVnsf1gH8xNy0nP/hCQ/mEDubE54sbVy7MH3+5LHlS2ejt39TVSvo90O40QJzDOY2JuDTMRbC0ENm264lU2w0RidTQvmf5IGQTSw/2N1lm63JH9Gp1RuT5CE8KQhcrbVRxS6VVIY1sntoJ8mld1GqKN64u55O8WQW7eUqsuwADMlDeGLYaHZ4wSozRqms84LJ8SYnWILosIzxLEaRlMpxVp5SMiRK7LCCaAIP1w6d9zu97SSbKyglziyxBsdbQkjaAKMKpouKJLmi6Aq8AyOSST6dEVyvht0xnKXE1ae7ZF6iGb3I6GXOgBQiAtiuVFyIA+Nk2asgzLEWhP97u1yRTNfzicHh4eofqWfR/F5BylKVXEGmWa1YViG5IITmJcZACMMUUQzPYlHbTjAcb4Rwt9tbXN2OxWmAU6QQ3cwXSwrNaCVGZTiD5U2Wt3mIIHkiwuWywbJWYocrFhUHu4Ru4JsLzzfjdCaHolvUn9HsnbsbaZILR7AaAzBnwuOLyAH/3YyYyYhbW0WSRJaF/wUqjPNz+4prxwAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/63581729799e31fcd1a0b0f4271c9cad/45876/avatar-big.png","srcSet":"/static/63581729799e31fcd1a0b0f4271c9cad/45876/avatar-big.png 1x,\n/static/63581729799e31fcd1a0b0f4271c9cad/eb85b/avatar-big.png 1.5x,\n/static/63581729799e31fcd1a0b0f4271c9cad/4f71c/avatar-big.png 2x,\n/static/63581729799e31fcd1a0b0f4271c9cad/9ec3e/avatar-big.png 3x"}}},"headerImage":null},"html":"<p><strong>The AUTO1 story started in 2012. In these six years company grew a lot. From one website\nwritten in PHP it evolved into a huge and complex system consisting of large number of components.\nThis article describes the evolution of AUTO1’s architecture from a monolithic approach to Microservices.</strong></p>\n<p>The brand independent automotive company owns business units like <strong>auto1.com</strong>, <strong>wirkaufendeinauto.de</strong> and <strong>autohero.com</strong>.</p>\n<h3>Original AUTO1 architecture</h3>\n<p>At the beginning we build several classic monolith applications that served different parts of business.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/70d9ecd1cafac00e7fe76b6f887ea877/08646/original-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: 67.62917933130699%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACMElEQVQ4y5VU22rbQBDV36eB9ql/UCg0JiGUQp1Q9waBEGI1bm21tRNcG1WSbdnWxdKudnU5nV1bqWX60oXRzp65MDtnVkZVVdhf9Xkf/x/MqJV4k6Asy4bTJkkhZd7AGOPgWdbAOM/AONe6TlhQovHvFcRB8MRdI9zwBjZbbTAnUavcYers+vEuIYEKjlJBiZvXiJhEXjSxNMvBRNHAEp6Dy+3tjEobSoh+G4nvQBZkoLPC5fcOUmeELK80plY+McEebsAoQVVuE+fTO/DRNVi+6yF5gztDlII1COLuCEUa1i3XX7m0IdduAxMKWznbCnMpKXOJmNqXkuR5riWThcaSR0zq6iNBZCmMdCmF7nssCaNilW4Mf9nonT7H5PwpBq1nGFgWbG+B7psXmJwd4+fJEcyba3j+GmbnNe5bT0iO8OVzGxNnDvPjW4xPjzEmrPvpAobjzWFdXeDr5Qn6H84xnU6xXAX40b1C/10L1vszPIyGWAUhRt9MDOg86LRwb/UwX64xMG/Qu3yFXvslrLtbIoWanRGTQZIhIQaFILbpioLasKaRiRWDNGPqeiwTehq27Je6DWpWVawSLmRNyt+lhluNQ7VjVc9pUSBJEr3XZNR+OHhpxja4KWmaIqPXwBjTiaIogu/7CMMQcRxru6pa2Q9jH59ePaSSWNd9XC7heR4cx4HrurBtG7PZTOuLxQJBEGi/uuo63vjXY1dJlaMaF7XrvtKuqla2ffzwJ/EH0qAxugLc5/wAAAAASUVORK5CYII='); 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=\"Original AUTO1 architecture\"\n        title=\"Original AUTO1 architecture\"\n        src=\"/static/70d9ecd1cafac00e7fe76b6f887ea877/40fad/original-architecture.png\"\n        srcset=\"/static/70d9ecd1cafac00e7fe76b6f887ea877/707e9/original-architecture.png 148w,\n/static/70d9ecd1cafac00e7fe76b6f887ea877/649e0/original-architecture.png 295w,\n/static/70d9ecd1cafac00e7fe76b6f887ea877/40fad/original-architecture.png 590w,\n/static/70d9ecd1cafac00e7fe76b6f887ea877/b3fef/original-architecture.png 885w,\n/static/70d9ecd1cafac00e7fe76b6f887ea877/301c0/original-architecture.png 1180w,\n/static/70d9ecd1cafac00e7fe76b6f887ea877/08646/original-architecture.png 1316w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>The more we grew the more harder it became to support this applications.\nThey did their job well but suffered from classic problems of monolith architecture.</p>\n<p>Our biggest pains were:</p>\n<ul>\n<li><strong>Code duplication</strong></li>\n<li><strong>Shared database</strong></li>\n<li><strong>Git Merge nightmare</strong></li>\n<li><strong>Codebase complexity</strong></li>\n<li><strong>No ownership</strong></li>\n<li><strong>Regression testing is almost impossible</strong></li>\n<li><strong>Long process from development to deploy</strong></li>\n</ul>\n<p>They became too big and too complex, so that adding new features and even fixing tiny bug was difficult and time consuming.\nAdditionally applications were using same database and had lots of shared code existing in separate repo.</p>\n<h3>Breaking down monolith to microservices</h3>\n<p>We decided to migrate to microservices step by step in several iterations.</p>\n<p>In the first phase we defined services corresponding to business capabilities.\nThen, piece by piece, we extracted functionality to separate services.</p>\n<p>These services mostly used the same database as the existing applications and served as a business logic layer for them.\nSo for example when user visited <em>auto1.com/my-orders</em>, he still did request to the monolith application. The application, in turn, performed a request to the needed microservices to get all the necessary information in order to respond properly</p>\n<p>Apart from that, for all new functionality we started building microservices with own database and own infrastructure. These new services also were integrated into the monolith in the same manner.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/ac45af715946c57a805d5a302c47dbc1/d6c21/breakdown-first-step.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.71573604060914%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsSAAALEgHS3X78AAAB3ElEQVQoz21SQWvUQBTO7xE8e/MnCF56KCJeRX+BUCw9eKg3F7SggqB7EjxYoR5kFUVZxEvBgqW1tCXZbJNsNpNMZjKTmXy+TBrZrQ58zLw389775r3Pw8JqmqbbZQ45vAub+rDTfVSvbju//voC6sMjaDqfnhwjTdO/cT28/rCY0CiBsxEFsxg8PEKwswVLV7PdEWbftwHJwHOGqqpwkZC3aDhY27EhVDXZtJeE1tuGs+AQ2eA69Dw8j7MwxsCex3lSSrT437LzAGp7A+rNPagvz2HqGjybITncRVnk0GQzxiCEAOccZVnCC+MM/rTrhZ75MOOXMHnsGKn9z+Brl8DvX0Y5uAYjCzQLBVsiQRAgiiJMJhNkWQbvZMrw209BvwMPDpC83YRMfNQUIPZGEA+uQG5ehdhaQZWnxMpAa+3YtYzaREmSIAxDFEUBr5QaXKhuGNT5nJe0d/2opkeYPL6FYLCK6PW6KyLJVw3vwLAIQlFS+mpNyZVSDt5FyfRLlYweGpiWKTFpk2lVQcY+wk9D8HnsCLTD6OGGsiwb63pkBcni2U3Y5Bg22IN4eqMr8vEJ5PuHLrk15p8hLslmSdhGQx6M0ZAeiySEP37nhhT9+oGzn9/6x0uC7vEHhMnzTu333rwAAAAASUVORK5CYII='); 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=\"Breakdown: first phase\"\n        title=\"Breakdown: first phase\"\n        src=\"/static/ac45af715946c57a805d5a302c47dbc1/40fad/breakdown-first-step.png\"\n        srcset=\"/static/ac45af715946c57a805d5a302c47dbc1/707e9/breakdown-first-step.png 148w,\n/static/ac45af715946c57a805d5a302c47dbc1/649e0/breakdown-first-step.png 295w,\n/static/ac45af715946c57a805d5a302c47dbc1/40fad/breakdown-first-step.png 590w,\n/static/ac45af715946c57a805d5a302c47dbc1/b3fef/breakdown-first-step.png 885w,\n/static/ac45af715946c57a805d5a302c47dbc1/d6c21/breakdown-first-step.png 985w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>Now every team has list of services it is responsible for. Each microservice has it’s own repository (some with an own database) and are responsible only for some specific part of the business.</p>\n<p>Services communicate with each other, there is not need to copy/paste code anymore, each service is the source of truth for its own responsibility.</p>\n<p>Additionally we introduced a separate deployment pipeline for each service. This allows us to work and release separately from other teams and reduces the amount of dependencies.</p>\n<p>Usual service architecture:\n<a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/b759907550fdf036e7b553ebb1454105/633da/basic-service.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: 46.12365063788027%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsSAAALEgHS3X78AAABgElEQVQoz31Sy0oDQRDMp/g5nvwHD+oXiAcfvxCI4MFDAnoQQQleo0EE0YuIiAcRs4gE3QTj7mZ3Mu8te2eyZhWxoaFnpqamprproMjz3GVZo7L+3gPtWQtrDCytdO8a+u4EuZZQWiNJYkgpUft5cUpuDRUWnHMwxtyZNRpScCgpIGk9PlzFuD4PnQwwZhxB0EOapqh5ML1qrVNRDaUUkQhXm2mWod+eMHm8RE4PldiCwxEOPmLESeovDgOI9jr0zREM8WtjkYsM4rQOftaAlV5xEocQilUs8VErVD33I/TDyOnL7jtI1+Yw2VuhCwa8yDAA215wyYcvDnd1vIHX2wNPSBaVfXAexilHNvFfU9E7os4O2EPXK9T0US0wuthH2G1BckZNMGDjEfn5p0L86ubMSUPeaq1cLaiDyvgT3qmTLZsEUB5bJSyl/hibostkcDEmbmCyEVhzEbLbcBjW3kLaWoIV/yisEs7S+pHJPhE1l5Gd73pbJtQkllbws19+AfjlsAHItSc6AAAAAElFTkSuQmCC'); 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=\"Usual service architecture\"\n        title=\"Usual service architecture\"\n        src=\"/static/b759907550fdf036e7b553ebb1454105/40fad/basic-service.png\"\n        srcset=\"/static/b759907550fdf036e7b553ebb1454105/707e9/basic-service.png 148w,\n/static/b759907550fdf036e7b553ebb1454105/649e0/basic-service.png 295w,\n/static/b759907550fdf036e7b553ebb1454105/40fad/basic-service.png 590w,\n/static/b759907550fdf036e7b553ebb1454105/b3fef/basic-service.png 885w,\n/static/b759907550fdf036e7b553ebb1454105/633da/basic-service.png 1019w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<p>The first phase was a great beginning of our journey to the world of microservices.\nWe got amazing results and most of our problems were solved:</p>\n<ul>\n<li>Code duplication <strong>Solved</strong></li>\n<li>Shared database Partially <strong>Partially Solved</strong></li>\n<li>Git Merge nightmare <strong>Solved</strong></li>\n<li>Codebase complexity <strong>Solved</strong></li>\n<li>No ownership <strong>Solved</strong></li>\n<li>Regression testing is almost impossible <strong>Solved</strong></li>\n<li>Long process from development to deploy <strong>Solved</strong></li>\n</ul>\n<p>Unfortunately there is no silver bullet. The microservice approach approach has its drawbacks and we faced some of them.</p>\n<ul>\n<li>System architecture complexity increased</li>\n<li>Development time for complex features increased</li>\n<li>We still had some parts of monolith that added complexity to development</li>\n</ul>\n<p>To resolve last of this problems we introduced <strong>the SPA approach</strong>.\nWe started using <a href=\"https://reactjs.org/\">React</a>. It allowed us to call microservices directly from the frontend with proxying requests through our monolith applications.\nTo be able to do so, we introduced simple proxy layers just for communicating with services.\nMore about this approach you can read in our <a href=\"https://auto1.tech/vigolante/\">blog article</a>.</p>\n<p>As a result our architecture took it’s next look:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/a6fe7ebe6a305ab8f472213e2f9001bd/68690/current-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: 70.03154574132492%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsSAAALEgHS3X78AAACkklEQVQ4y3VT2W7TQBT1pyPxxCtCAgkeQEKqKGp5qQpVC0VpBah707Rp4iSOl3HsLN73JYc70wZMgZGuMuPc7Zx7rqToFrIsR1VViKIIiqKAMQbf98VbWBgiSgt4ahdp+xOSokISxyiKAvwsl0th/EhD5sDzPBHoui46nQ5kWRbOK8vzHEVZIo1DTPUR6roWDXBbJVuZVPMLfp+CgnnimAqEcUqWIE1ipGmC2WyOhevDDSKUVOBfR8pCD4vrQ4QEaUbObDqH4zio6U9XPkMwUWB7Mey5C2s6Qx4soP34iLFuwjAtmNYUmmFiNneIuow6LFJ4xgCBISNULhEOT+Es5siLEsnchK/3kKptxFpHFKqyBFr3HPJNG8rVMUadU7ITyNcXYOYEElEKJykx3F/HZPcVzO1nGI8GKIm7jLjoH+3D2ngMtvUU/e61gHU7NnG2+QKjtUforz+BvPYYJxvPMVAZdUgE51mKioIdP0KQZMQXcRcEyOmXDyUQXKZUJCffDLY1wVhVMRwpuO31RWfMNMVwpebYM0rs84mTTOKihkPkZxXAJjZUnSHOa7hhgiSvRGKuDM7bH0NZNqbMu+VQbSeA2XoH82QH9vcPYGdfoF+0YB++h320Bet4m+AZYvo8Bk3ZNEXJdZXnGUEPwS4PYPXOYbUPwLqnYL0Lurcw6XyDeUVmzyhhImKaKKWHSucCXtb3TvedRwQ/zO50Vze+cy2u4n5tSvPBq3FO/JC2RicZaTcIzQHdB1gYQ0T0DhhJTOtitnCRUIcC8v8S8opVWcBaeJC/bsLYew3181uorQ0oR7vQd16Ku7r3BrcjTexzE/JfCcWk+d6S5eXdRDlXfBtsWjtm0+p5AdJyScXvdvwh5J8zVyN6ov+4DAAAAABJRU5ErkJggg=='); 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=\"Current architecture\"\n        title=\"Current architecture\"\n        src=\"/static/a6fe7ebe6a305ab8f472213e2f9001bd/40fad/current-architecture.png\"\n        srcset=\"/static/a6fe7ebe6a305ab8f472213e2f9001bd/707e9/current-architecture.png 148w,\n/static/a6fe7ebe6a305ab8f472213e2f9001bd/649e0/current-architecture.png 295w,\n/static/a6fe7ebe6a305ab8f472213e2f9001bd/40fad/current-architecture.png 590w,\n/static/a6fe7ebe6a305ab8f472213e2f9001bd/b3fef/current-architecture.png 885w,\n/static/a6fe7ebe6a305ab8f472213e2f9001bd/301c0/current-architecture.png 1180w,\n/static/a6fe7ebe6a305ab8f472213e2f9001bd/68690/current-architecture.png 1268w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<h3>Results</h3>\n<p>For the last six years we passed a long way from old fashioned php websites to huge distributed net of microservices.\nHere are some numbers at the end of 2018:</p>\n<ul>\n<li><strong>Over 250 microservices created</strong></li>\n<li><strong>Over 655 repositories</strong></li>\n<li><strong>Over 30 application databases</strong></li>\n<li><strong>Written on Java, PHP, Go, NodeJS</strong></li>\n<li><strong>Over 200 Engineers</strong></li>\n<li><strong>More than 20 teams (some of them are remote)</strong></li>\n<li><strong>Each team owns 5-20 services</strong></li>\n<li><strong>Average 10 releases per day</strong></li>\n</ul>\n<p>Microservices brought us lots of advantages, but along with advantages new challenges had come.</p>","fields":{"slug":"/migration-to-microservices/","tags":["auto1","engineering","monolith","microservices"]}}}]}},"pageContext":{"slug":"/categories/Architecture","category":"Architecture","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}