{"id":35906,"date":"2019-10-31T22:07:29","date_gmt":"2019-10-31T19:07:29","guid":{"rendered":"https:\/\/prohoster.info\/blog\/perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika\/"},"modified":"2019-10-31T22:07:29","modified_gmt":"2019-10-31T19:07:29","slug":"perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika","status":"publish","type":"post","link":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika","title":{"rendered":"Transitioning from monoliths to microservices: history and practice.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>In this article, I will explain how the project I am working on evolved from a large monolith into a set of microservices.<\/p>\n<p>The project began its history quite a while ago, in the early 2000s. The first versions were written in Visual Basic 6. Over time, it became clear that developing in this language would be difficult to maintain in the future, as both the IDE and the language evolved slowly. At the end of the 2000s, it was decided to transition to the more promising C#. A new version was being developed alongside improvements to the old one, and gradually more code was migrated to .NET. The C# backend initially focused on a service-oriented architecture; however, shared libraries with logic were used during development, and services were launched in a single process. This resulted in an application we called a \u201cservice monolith.\u201d <\/p>\n<p>One of the few advantages of such a setup was the ability for services to call each other via an external API. There were clear prerequisites for transitioning to a more proper service-oriented, and eventually microservice architecture. <\/p>\n<p>We began our work on decomposition around 2015. While we haven't yet reached an ideal state\u2014there are parts of the large project that are difficult to call monoliths, but neither do they look like microservices\u2014significant progress has been made. <br \/>\nThis is what I will discuss in the article.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/132bb4ea6b9dbcdd202ee090f2b86289.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h3>Content<\/h3>\n<p><\/p>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"#1\"> Architecture and Problems of the Existing Solution<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#2\">Expectations from Microservices<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#3\">Transition Challenges<\/a><\/noindex><\/li>\n<li> <noindex><a rel=\"nofollow\" href=\"#4\">How to Transition from Monoliths to Microservices<\/a><\/noindex>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"#5\">The first method<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#6\">The second method<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#7\">Third Approach<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#8\">Fourth Approach<\/a><\/noindex><\/li>\n<\/ul>\n<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#9\">Working with the Database<\/a><\/noindex>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"#10\">Separation of Existing Tables<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#11\">Separation with Redesign<\/a><\/noindex><\/li>\n<\/ul>\n<\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#12\">Working with Source Code<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#13\">Infrastructure Issues<\/a><\/noindex>\n<ul>\n<li><noindex><a rel=\"nofollow\" href=\"#16\">Manual Installation in Environments<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#14\">Separate Logging<\/a><\/noindex><\/li>\n<li><noindex><a rel=\"nofollow\" href=\"#15\">Testing and Debugging Related Services<\/a><\/noindex><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><noindex><a rel=\"nofollow\" name=\"1\"><\/a><\/noindex><b><\/p>\n<h3>Architecture and Problems of the Existing Solution<\/h3>\n<p><\/b><br \/>\nInitially, the architecture looked as follows: the UI was a separate application, the monolithic part was written in Visual Basic 6, and the .NET application comprised a set of interconnected services working with a fairly large database.<\/p>\n<p><b>Drawbacks of the Previous Solution<\/b><\/p>\n<p><u>Single Point of Failure<\/u><br \/>\nWe had a single point of failure: the .NET application ran in one process. If any module failed, the whole application would crash and need to be rebooted. Since we automate a large number of processes for different users, a failure in one of them could prevent everyone from working for a while. Even with redundancy, a programming error would not help. <\/p>\n<p><u>Queue for improvements<\/u><br \/>\nThis drawback is more organizational. Our application has many customers, and they all want their improvements as quickly as possible. Previously, it was impossible to address these requests simultaneously, resulting in a queue of customers. This process created frustration for the business, as they had to prove that their task was valuable. Meanwhile, the development team spent time organizing this queue, which consumed a lot of time and effort, and ultimately the product could not evolve as quickly as desired.<\/p>\n<p><u>Suboptimal resource utilization<\/u><br \/>\nWhen deploying services in a single process, we always fully copied the configuration from server to server. We wanted to place the most heavily loaded services separately to avoid wasting resources and achieve more flexible management of our deployment scheme.<\/p>\n<p><u>Difficulties implementing modern technologies<\/u><br \/>\nA familiar issue for all developers: there is a desire to implement modern technologies into the project, but no opportunity to do so. In a large monolithic solution, updating the current library, let alone switching to a new one, becomes quite a non-trivial task. It takes a long time to convince the team leader that this will yield more benefits than stress incurred. <\/p>\n<p><u>Difficulty in releasing changes<\/u><br \/>\nThis was the most serious problem \u2014 we were releasing updates every two months. <br \/>\nEach release turned into a real disaster for the bank, despite testing and the developers' efforts. The business understood that part of the functionality would not work at the beginning of the week. Meanwhile, the developers braced themselves for a week of serious incidents. <br \/>\nEveryone wanted to change the situation. <\/p>\n<p><noindex><a rel=\"nofollow\" name=\"2\"><\/a><\/noindex><b><\/p>\n<h3>Expectations from Microservices<\/h3>\n<p><\/b><br \/>\n<u>Release components as they become ready. <\/u>Release components as they become ready through the decomposition of the solution and separation of various processes.<\/p>\n<p><u>Small product teams.<\/u> This is important because managing a large team working on an old monolith was challenging. Such a team was forced to adhere to a strict process, whereas more creativity and independence were desired. Only small teams could afford this.<\/p>\n<p><u>Isolation of services in separate processes.<\/u> Ideally, we wanted to isolate them in containers, but a large number of services written on .NET Framework only run on Windows. Currently, services on .NET Core are emerging, but they are still few.<\/p>\n<p><u>Deployment flexibility.<\/u> We want to combine services as needed, rather than being dictated by the code.<\/p>\n<p><u>Adoption of new technologies.<\/u> This is interesting to any programmer.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"3\"><\/a><\/noindex><b><\/p>\n<h3>Transition Challenges<\/h3>\n<p><\/b><br \/>\nOf course, if breaking the monolith into microservices were easy, it wouldn't be a topic of discussion at conferences or in articles. There are many pitfalls in this process; I will outline the main ones that hindered us.<\/p>\n<p><b>The first problem<\/b> is typical for most monoliths: tight coupling of business logic. When we write a monolith, we aim to reuse our classes to avoid writing redundant code. However, transitioning to microservices complicates this: all code is tightly connected, making it difficult to separate services.<\/p>\n<p>At the start of the work, the repository had over 500 projects and more than 700,000 lines of code. This is quite a large solution and <b>the second problem<\/b>. Simply dividing it into microservices seemed impossible.<\/p>\n<p><b>The third problem<\/b> \u2014 lack of necessary infrastructure. Essentially, we were manually copying source code to servers.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"4\"><\/a><\/noindex><b><\/p>\n<h3>How to Transition from Monoliths to Microservices<\/h3>\n<p><\/b><br \/>\n<u>Identifying microservices<\/u><\/p>\n<p>First, we immediately determined that splitting microservices is an iterative process. We were always required to simultaneously develop business tasks. How we would technically implement this was our problem. Therefore, we prepared for an iterative process. It\u2019s the only way if you have a large application that wasn't initially ready to be rewritten.<\/p>\n<p>What methods do we use to identify microservices?<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"5\"><\/a><\/noindex><b>The first method <\/b>\u2014 to implement existing modules as services. Fortunately, we already had established services that operated over the WCF protocol. They were separated into distinct assemblies. We migrated them individually, adding a small startup module to each assembly. This was written using the wonderful Topshelf library, which allows the application to run both as a service and as a console. This is convenient for debugging, as it eliminates the need for additional projects in the solution.<\/p>\n<p>The services were intertwined by business logic, as they used common assemblies and worked with a shared database. They were hard to classify as pure microservices. Nevertheless, we could expose these services separately in different processes. This alone reduced their interdependencies, mitigating the issues with parallel development and single points of failure.<\/p>\n<p>The assembly with the host consists of just one line of code in the Program class. We encapsulated the work with Topshelf in a helper class.<\/p>\n<pre><code class=\"plaintext\">namespace RBA.Services.Accounts.Host\n{\n   internal class Program\n   {\n      private static void Main(string[] args)\n      {\n        HostRunner.Run(\"RBA.Services.Accounts.Host\");\n\n       }\n    }\n}\n<\/code><\/pre>\n<p>\n<noindex><a rel=\"nofollow\" name=\"6\"><\/a><\/noindex><b>The second way to delineate microservices:<\/b> is to create them to solve new tasks. If the monolith does not grow in the process, that\u2019s great; it means we are moving in the right direction. For solving new tasks, we aimed to create separate services. If possible, we made more \"canonical\" services that fully manage their data model and have separate databases. <\/p>\n<p>Like many others, we started with authentication and authorization services. They are perfect for this. They are independent, typically possessing a distinct data model. They do not interact with the monolith; only it calls them to solve certain tasks. These services can serve as a foundation for transitioning to a new architecture, debugging the infrastructure based on them, trying out approaches related to networking libraries, etc. In our organization, there are no teams that have struggled to create an authentication service. <\/p>\n<p><noindex><a rel=\"nofollow\" name=\"7\"><\/a><\/noindex><b>The third way to delineate microservices<\/b>, which we use, is somewhat specific to us. This involves extracting business logic from the UI layer. Our main UI application is desktop-based and, like the backend, is written in C#. Developers occasionally made mistakes by pushing parts of the logic that should exist in the backend into the UI to be reused. <\/p>\n<p>If we look at a real example from the UI code, we can see that a large part of this solution contains true business logic that is useful in other processes, not just for building UI forms. <\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/74c7b90ff94b343816ab3ac2fa0673c6.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe real logic in the UI is only in the last couple of lines. We moved it to the server so that it could be reused, thereby reducing the UI and achieving the right architecture.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"8\"><\/a><\/noindex><b>The fourth and most important way to extract microservices<\/b>, which allows us to reduce the monolith, is to extract existing services with modification. When we extract existing modules as they are, the outcome doesn't always please developers, and the business process since the functionality was created might have become outdated. Through refactoring, we can support the new business process because business requirements are constantly changing. We can improve the source code, eliminate known defects, and create a higher-quality data model. A lot of advantages accumulate.<\/p>\n<p>Separating services with modification is inherently linked to the concept of a bounded context. This concept comes from domain-driven design. It refers to a part of the domain model where all terms of the ubiquitous language are clearly defined. Let's consider the example of the context of insurance and accounts. We have a monolithic application, and we need to work with the account in insurance. We expect the developer to find an existing class 'Account' in another assembly, reference it from the 'Insurance' class, and we will get working code. The DRY principle will be adhered to, and the task will be completed faster by leveraging existing code.<\/p>\n<p>It turns out that the contexts of accounts and insurance are interconnected. When new requirements arise, this connection will complicate development, increasing the complexity of the already intricate business logic. To solve this problem, it's necessary to identify the boundaries between contexts in the code and eliminate any violations of these boundaries. For instance, the insurance context may only require a 20-digit account number from the Central Bank and the account opening date. <\/p>\n<p>To separate these limited contexts from one another and initiate the process of extracting microservices from a monolithic solution, we adopted an approach of creating external APIs within the application. If we knew that a certain module needed to become a microservice or to change in the process, we would immediately make calls to the logic belonging to another limited context through external calls, such as via REST or WCF.<\/p>\n<p>We firmly decided not to shy away from code that would require implementing distributed transactions. In our case, it turned out to be relatively easy to adhere to this rule. We have not encountered situations that actually required strict distributed transactions \u2014 it has been sufficient to ensure eventual consistency between modules.<\/p>\n<p>Let's consider a specific example. We have the concept of an orchestrator \u2014 a pipeline that processes the entity 'application'. It sequentially creates a client, an account, and a bank card. If the client and account are created successfully but the card creation fails, the application does not transition to the 'successful' status and remains in the 'card not created' status. In the future, background tasks will pick it up and complete it. The system remains in a state of inconsistency for some time, but this is generally acceptable to us.<\/p>\n<p>In the event that a situation arises where we need to consistently save part of the data, we will likely opt to consolidate the service to handle this in a single process. <\/p>\n<p>Let's look at an example of extracting a microservice. How can we safely bring it to production? In this example, we have a separate part of the system \u2014 the payroll service module, one of the sections of which we would like to transform into a microservice.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/346af271b0c3f99897d330e56f713f18.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nFirst, we create a microservice by rewriting the code. We enhance certain aspects that we were not satisfied with. We implement new business requirements from the client. We introduce an API Gateway into the connection between the UI and the backend, which will handle the call forwarding. <\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/bec8d68f20f3ec53af0cef225a51c2b3.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNext, we deploy this configuration into production, but in a pilot state. Most of our users are still working with the old business processes. For new users, we are developing a new version of the monolithic application that no longer includes this process. Essentially, we have a combination of the monolith and the microservice working as a pilot.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/27e98812746bb7f142fa3a3c3a03b52f.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWith a successful pilot, we understand that the new configuration is indeed functional, and we can eliminate the old monolith from the equation, leaving the new configuration in place of the old solution.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/11dcf1d771b57d3921c0aa91b82646e1.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn summary, we are utilizing almost all existing methods for separating the source code of the monolith. All of them allow us to reduce the size of application components and transition them to new libraries, resulting in higher quality source code.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"9\"><\/a><\/noindex><b><\/p>\n<h3>Working with the Database<\/h3>\n<p><\/b><br \/>\nThe database is less amenable to separation than the source code, as it contains not only the current schema but also accumulated historical data.<\/p>\n<p>Our database, like many others, had another significant drawback \u2014 its enormous size. This database was designed according to the complex business logic of the monolith, and connections accumulated between tables of various bounded contexts.<\/p>\n<p>In our case, adding to all the troubles (a large database, numerous connections, sometimes unclear boundaries between tables), we encountered an issue common in many large projects: the use of the shared database pattern. Data was drawn from tables via views, replicated, and shipped to other systems that required this replication. As a result, we could not move tables into a separate schema because they were actively being used.<\/p>\n<p>What helps us in the separation is the division into bounded contexts in the code. It typically gives us a fairly good understanding of how we partition data at the database level. We know which tables belong to one bounded context and which belong to another.<\/p>\n<p>We have applied two global methods for database separation: separating existing tables and separating with refactoring.<\/p>\n<p>Separating existing tables is a method that works well when the data structure is of high quality, meets business requirements, and satisfies everyone involved. In this case, we can extract existing tables into a separate schema.<\/p>\n<p>Separation with refactoring is necessary when the business model has significantly changed, and the tables no longer meet our needs.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"10\"><\/a><\/noindex><b>Separating existing tables.<\/b> We need to determine what we will separate. Without this knowledge, nothing can be done, and here separation of limited contexts in the code will help us. Typically, if we can understand the boundaries of contexts in the source code, it becomes clear which tables should be included in the separation list.<\/p>\n<p>Let\u2019s imagine that we have a solution in which two modules of a monolith interact with a single database. We need to ensure that only one module interacts with the section of the tables being separated, while the other starts interacting with it through the API. Initially, it\u2019s sufficient for the API to only handle write operations. This is a necessary condition for us to discuss the independence of microservices. Read connections can remain while there is no major issue.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/1602637ad752ac055f475cfa45d95e95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe next step is that we can already extract the section of the code that works with the separable tables, with or without refactoring, into a separate microservice and run it in a separate process or container. This will be a separate service with connections to the monolith's database and those tables not directly related to it. The monolith continues to read from the separable part. <\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/36011f4e6be4f6a2f19f2f074b645dcb.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLater, we will remove this connection, meaning that the reading of data from the separable tables in the monolithic application will also be transitioned to the API.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/290f91bbfccac2a4b384078e4cf4e337.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNext, we will extract from the main database the tables that are only used by the new microservice. We can move the tables to a separate schema or even to a separate physical database. There remains a read connection between the microservice and the monolith's database, but this poses no significant issue; in such a configuration, it can operate for quite a long time.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/ab947ac6a38ffbccd4e2b51103aef609.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe final step is to completely remove all connections. In this case, we may need to migrate data from the main database. Sometimes we want to reuse data or reference information replicated from external systems across several databases. We encounter this periodically.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/f40cf17dd56b9575751e370c44f08344.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<noindex><a rel=\"nofollow\" name=\"11\"><\/a><\/noindex><b>Department with reprocessing.<\/b> This method is very similar to the first, but it goes in reverse order. We immediately \u0432\u044b\u0434\u0435\u043b\u044f\u0435\u043c a new database and a new microservice that interacts with the monolith via API. However, we still have a set of database tables that we want to remove in the future. We no longer need it; in the new model, we've replaced it.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/c94932033e47cfa1fd56595ab9a19246.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nFor this schema to work, we will most likely need a transitional period.<\/p>\n<p>Next, there are two possible approaches.<\/p>\n<p><b>First<\/b>: we duplicate all data in the new and old databases. In this case, we have data redundancy, which can lead to synchronization issues. However, we can have two different clients. One will work with the new version, and the other with the old one.<\/p>\n<p><b>Second<\/b>: we separate the data by some business criteria. For example, in our system, there were 5 products stored in the old database. The sixth, as part of a new business task, we place in the new database. But we will need an API Gateway to synchronize this data and show the client where and what to take.<\/p>\n<p>Both approaches are viable; choose based on the situation.<\/p>\n<p>After we ensure everything is working, the part of the monolith that interacts with the old database structures can be disabled. <\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/973bf5015bdf49a290a5b901f89628cc.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nThe last step will be to delete the old data structures. <\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/fe83acc07077b7eb3717138e3c20c005.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn summary, we can say that we have problems with the database: it is more difficult to work with compared to the source code, and separation is harder, but it can and should be done. We have found some ways to do this safely; after all, it's easier to make a mistake with data than with source code. <\/p>\n<p><noindex><a rel=\"nofollow\" name=\"12\"><\/a><\/noindex><b><\/p>\n<h3>Working with Source Code<\/h3>\n<p><\/b><br \/>\nThis is what the source code schema looked like when we started analyzing the monolithic project.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/a6d886e37117ccf8f63106d6616aa653.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIt can conditionally be divided into three layers. This is the layer of runnable modules, plugins, services, and individual activities. In fact, these were entry points within a monolithic solution. All of them were tightly bound by the Common layer. It contained the business logic that was used jointly by the services and numerous connections. Each service and plugin utilized 10 or more common assemblies, depending on their size and the integrity of the developers.<\/p>\n<p>We were fortunate to have infrastructure libraries that could be used separately. <\/p>\n<p>Sometimes there were situations when certain Common objects actually did not belong to this layer, but were infrastructure libraries. This was resolved by renaming.<\/p>\n<p>Limited contexts were the most concerning. There were times when 3-4 contexts were mixed in a single Common assembly and used each other within the same business functions. It was necessary to understand where this could be separated and what the boundaries were, as well as what to do with the mapping of this separation to the source code assemblies.<\/p>\n<p>We formulated several rules for the code separation process.<\/p>\n<p><b>First<\/b>: we no longer wanted to share business logic among services, activities, and plugins. We wanted to make the business logic independent within microservices. On the other hand, microservices, ideally, are perceived as services that exist completely independently. I believe this approach is somewhat wasteful and difficult to achieve since, for example, services in C# will inevitably be connected by the standard library. Our system is written in C#, and we have not had to use other technologies so far. Therefore, we decided that we could afford to use common technical assemblies, as long as they did not contain any fragments of business logic. If you have a convenient wrapper over the ORM you are using, copying it from service to service is very costly.<\/p>\n<p>Our team is a fan of domain-driven design, so 'onion architecture' suited us perfectly. The basis of our services became not the data access layer, but an assembly with domain logic that contains only business logic and is free from ties to the infrastructure. This way, we can independently improve the domain assembly to solve problems related to frameworks.<\/p>\n<p>At this stage, we encountered our first serious problem. The service needed to refer to a single domain assembly, while we wanted the logic to be independent, and the DRY principle was significantly hindering us. The developers wanted to avoid duplication by reusing classes from adjacent assemblies, and as a result, domains started to become interlinked again. We analyzed the results and concluded that the problem might also lie in how the source code repository was structured. We had a large repository containing all the source codes. It was very difficult to build the solution for the entire project on a local machine. Therefore, separate small solutions were created for different parts of the project, and no one prevented adding some common or domain assembly for reuse. The only tool that didn't allow us to do this was code review, but sometimes it failed as well.<\/p>\n<p>We then began transitioning to a model with separate repositories. Business logic ceased to leak from service to service, and domains truly became independent. Bounded contexts are now supported more clearly. How do we reuse infrastructure libraries in this model? We isolated them into a separate repository, then packaged them into NuGet packages, which we placed in Artifactory. With any changes, building and publishing occurs automatically.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/8ddbc750dc7c6d397a459acf7825de90.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOur services began referencing internal infrastructure packages in the same way as external ones. We download external libraries from NuGet. To work with Artifactory, where we placed these packages, we used two package managers. In smaller repositories, we continued to use NuGet. In repositories with multiple services, we used Paket, which ensures greater version consistency among modules.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/672a25a70a481bff68baebe963184ccd.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nBy working on the source code, slightly modifying the architecture and separating repositories, we make our services more independent.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"13\"><\/a><\/noindex><b><\/p>\n<h3>Infrastructure Issues<\/h3>\n<p><\/b><br \/>\nMost of the pitfalls when transitioning to microservices are related to infrastructure. You will need automated deployment and new libraries to manage the infrastructure.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"16\"><\/a><\/noindex><b>Manual Installation in Environments<\/b><\/p>\n<p>Initially, we set up the environments manually. To automate this process, we created a CI\/CD pipeline. We chose a continuous delivery process because continuous deployment is not yet acceptable for us from a business process perspective. Therefore, deployment is done by button press, while testing is automatic.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/0fc092326a48813398c6f3a031197bab.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWe use Atlassian, Bitbucket for source code storage, and Bamboo for building. We prefer to write build scripts in Cake because it\u2019s the same as C#. Ready packages come into Artifactory, and Ansible automatically deploys them to test servers, after which they can be tested immediately.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/fcc743cfaa4a24b906ceeb40ec1ba0a0.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<noindex><a rel=\"nofollow\" name=\"14\"><\/a><\/noindex><b><\/p>\n<h3>Separate Logging<\/h3>\n<p><\/b><br \/>\nAt one time, one of the ideas behind the monolith was to ensure combined logging. We also needed to figure out what to do with separate logs that reside on disks. Logs are written to text files. We decided to use the standard ELK stack. We didn\u2019t write directly to ELK through providers, but instead decided to enhance the text logs to record the trace ID as an identifier, adding the service name so that these logs could later be parsed.<\/p>\n<p><img decoding=\"async\" alt=\"Transitioning from monoliths to microservices: history and practice.\" src=\"\/wp-content\/uploads\/2019\/07\/8e58c66ac134e65abe34b59939f31483.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWith Filebeat, we can collect our logs from <a class=\"wpil_keyword_link\" href=\"https:\/\/prohoster.info\/en\/server\/\"   title=\"servers\" data-wpil-keyword-link=\"linked\"  data-wpil-monitor-id=\"1338\">servers<\/a>, then transform them, build queries in the UI with Kibana, and see how the calls were made between services. The trace ID helps significantly with this.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"15\"><\/a><\/noindex><b><\/p>\n<h3>Testing and Debugging Related Services<\/h3>\n<p><\/b><br \/>\nInitially, we did not fully understand how to debug the services we were developing. With a monolith, everything was straightforward; we launched it on a local machine. We tried to do the same with microservices, but sometimes launching a single microservice requires starting several others, which is inconvenient. We realized that we needed to transition to a model where we only leave the service or services we want to debug on the local machine. Other services are accessed from servers matching the production configuration. After debugging, during testing, only the modified services are deployed to the test server for each task. This way, the solution is tested in the form it will eventually take in production.<\/p>\n<p>There are servers on which only production versions of the services are installed. These servers are needed in case of incidents, for verifying delivery before deployment, and for internal training.<\/p>\n<p>We have introduced an automated testing process using the popular Specflow library. Tests are automatically triggered with NUnit immediately after deployment via Ansible. If the task coverage is fully automated, there is no need for manual testing. However, sometimes additional manual testing is still required. To determine which tests to run for a specific task, we use tags in Jira.<\/p>\n<p>Additionally, the demand for load testing has increased; it was previously conducted only in rare cases. We use JMeter to run the tests, InfluxDB to store them, and Grafana to visualize the process.<\/p>\n<p><b><\/p>\n<h3>What have we achieved?<\/h3>\n<p><\/b><br \/>\nFirstly, we have eliminated the concept of a 'release.' The monstrous bi-monthly releases, which would deploy this behemoth into the production environment, disrupting business processes for a time, are gone. Now, we deploy services on average every 1.5 days, grouping them, because they go into operation after approval.<\/p>\n<p>There are no fatal failures in our system. If we release a microservice with a bug, the functionality related to it will be broken, but all other functionality will remain intact. This significantly improves the user experience.<\/p>\n<p>We can manage the deployment scheme. It's possible to allocate groups of services separately from the rest of the solution if necessary.<\/p>\n<p>Additionally, we have significantly reduced the issue of a large backlog of modifications. We have separate product teams that work with some services independently. Here, the Scrum process fits quite well. A specific team may have its own product owner who sets tasks for them. <\/p>\n<p><b><\/p>\n<h3>Summary<\/h3>\n<p><\/b><\/p>\n<ul>\n<li>Microservices are well-suited for decomposing complex systems. Throughout the process, we begin to understand what exists in our system, what limited contexts we have, and where their boundaries lie. This allows us to properly distribute modifications across modules and avoid code entanglement. <\/li>\n<li>Microservices provide organizational advantages. They are often only discussed as architecture, but any architecture is needed to meet business needs, not for its own sake. Therefore, we can say that microservices are well-suited for solving tasks with small teams, considering that Scrum is very popular now.<\/li>\n<li>Separation is an iterative process. You can't just take an application and split it into microservices. The resulting product is unlikely to be functional. When delineating microservices, it's beneficial to rewrite existing legacy code, turning it into code that we like and better meets business needs for functionality and speed.\n<p><i>A small warning:<\/i> the costs of transitioning to microservices are quite significant. A lot of time has already been spent on solving the infrastructure problem. Therefore, if you have a small application that does not require specific scaling, and there isn't a large number of clients competing for your team's attention and time, then perhaps microservices are not what you need today. It can be quite expensive. If you start the process with microservices, the initial costs will be higher than starting with the development of a monolith for the same project. <\/p>\n<p>P.S. A more emotional story (as if personally for you) \u2013 by <noindex><a rel=\"nofollow\" href=\"https:\/\/www.youtube.com\/watch?v=qTNbx18DzpQ\">this link<\/a><\/noindex>. <br \/>\nHere is the full version of the report.<\/li>\n<\/ul>\n<p>Source: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/raiffeisenbank\/blog\/458404\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e, \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u043b\u0441\u044f \u0438\u0437 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430 \u0432 \u043d\u0430\u0431\u043e\u0440 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u041f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430\u0447\u0430\u043b \u0441\u0432\u043e\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0434\u0430\u0432\u043d\u043e, \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 2000. \u041f\u0435\u0440\u0432\u044b\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0431\u044b\u043b\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u043d\u0430 Visual Basic 6. \u0421 \u0442\u0435\u0447\u0435\u043d\u0438\u0435\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0441\u0442\u0430\u043b\u043e \u043f\u043e\u043d\u044f\u0442\u043d\u043e, \u0447\u0442\u043e \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0443 \u043d\u0430 \u044d\u0442\u043e\u043c \u044f\u0437\u044b\u043a\u0435 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c, \u0442\u0430\u043a \u043a\u0430\u043a IDE [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":26858,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-35906","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e, \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u043b\u0441\u044f \u0438\u0437 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430 \u0432 \u043d\u0430\u0431\u043e\u0440 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u041f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430\u0447\u0430\u043b \u0441\u0432\u043e\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0434\u0430\u0432\u043d\u043e, \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 2000.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043e\u0442 \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430 \u043a \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c: \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e, \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u043b\u0441\u044f \u0438\u0437 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430 \u0432 \u043d\u0430\u0431\u043e\u0440 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u041f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430\u0447\u0430\u043b \u0441\u0432\u043e\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0434\u0430\u0432\u043d\u043e, \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 2000.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:07:29+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:07:29+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Transitioning from Monolith to Microservices: History and Practice | ProHoster","description":"In this article, I will discuss how the project I am working on transformed from a large monolith into a set of microservices. The project began its journey quite some time ago, in the early 2000s.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043e\u0442 \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430 \u043a \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u043c: \u0438\u0441\u0442\u043e\u0440\u0438\u044f \u0438 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430 | ProHoster","og:description":"\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u043f\u0440\u043e\u0435\u043a\u0442, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u044f \u0440\u0430\u0431\u043e\u0442\u0430\u044e, \u043f\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u043b\u0441\u044f \u0438\u0437 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430 \u0432 \u043d\u0430\u0431\u043e\u0440 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432. \u041f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430\u0447\u0430\u043b \u0441\u0432\u043e\u044e \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0434\u0430\u0432\u043d\u043e, \u0432 \u043d\u0430\u0447\u0430\u043b\u0435 2000.","og:url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/perehod-ot-monolita-k-mikroservisam-istoriya-i-praktika","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:07:29+00:00","article:modified_time":"2019-10-31T19:07:29+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"35906","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-02-09 17:04:56","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:54:38","updated":"2026-02-09 17:04:56","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/35906","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=35906"}],"version-history":[{"count":1,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/35906\/revisions"}],"predecessor-version":[{"id":158582,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/35906\/revisions\/158582"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/26858"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=35906"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=35906"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=35906"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}