• Nebyly nalezeny žádné výsledky

Another significant conundrum is the data storing in databases; each of the two architectures has an entirely distinct technique in this matter (RQ3, chapter 3.4, and the problem mentioned in chapter 2.7.2). There were three main problems to be found mentioned in the interviews that microservices have:

• Database schema split

• Referential integrity

• Transactions

With monoliths, it is dead simple to manage a database as monoliths using just one large schema (Newman, 2019). In contrary, microservices provide an interesting design puzzle.

Website microservice.io mentions several data storage patterns that can be used to maintain data consistency and implement queries (Chris Richardson Consulting, Inc., 2017a). We will discuss three of them as a solution to each of the mentioned problems above.

4.4.1 Pattern: Shared database

This simple solution is somewhat a hybrid with the monolithic approach – a single database is shared among multiple services. Each of them has full access to data owned by other services, using local ACID transactions (Chris Richardson Consulting, Inc., 2017a). This approach has obviously some advantages and some downsides.

The benefit is of course the simplicity of the database operation as well as easier usages as it resembles the monolithic approach. Among the drawbacks there is the most significantly the coupling of all the services with each other, thus basically breaking the microservice principle of the total (or as high as possible) independency. One service can then slow or block the others, for example if two services want to access the same table simultaneously, but only one of them can own the lock.

However, there is no reason for database splitting, referential integrity is guaranteed, and transactions can run on the whole database – although slowing or blocking each other as mentioned above.

4.4.2 Pattern: Database per service

In this pattern, database is split into separate schemas, where each service persists data that it owns. This data can then only be accessed via the service API (Chris Richardson Consulting, 2017c).

Advantages of this approaches include loose coupling as changes in one database or schema cannot affect the other. Another advantage is then entirely technical independence, meaning that each service can implement any database they desire. The important part that needs to be aligned is just the data. Nonetheless, there are many severe drawbacks.

Guaranteeing both the referential integrity is very difficult, as the database schemas are joined on agreed data model and data itself. It is basically an emphasized version of the interface contracts. Similarly, handling transactions that span multiple service is also very challenging as in this pattern each service is supposed to handle only their own transactions.

4.4.3 Pattern: Saga

A saga is a transaction that spans multiple services (Chris Richardson Consulting, Inc., 2017b). It is basically a sequence of local transactions, each updating its schema and sending an event to trigger the next transaction in the sequence. Likewise, if data violation is encountered, one transaction can send a signal to initiate rollbacks on the preceding transactions.

There are two ways of implementing this:

• Choreography: one local transaction triggers another local transaction in some other service.

• Orchestration: one managing orchestrator directs the individual services to execute any of their local transactions.

This approach allows microservice applications to manage data consistency, while keeping the loose coupling of individual schemas. The disadvantage is the complexity of the process – an experienced database expert is required to setup these transactions properly.

4.4.4 Demonstration on the developed examples

Database used in monolith and the Shared database pattern for microservices allow to manage e-shop transactions on the simplest level possible, as there are no extra constraints.

Database per service pattern would handle well business cases that target only one service – like creating a customer account or lowering the stock of a product when after a customer purchase.

Yet, let us imagine a scenario, such as creating a new order. Since it includes many of the shop services to work together, working without any kind of managing directive is near to impossible. Thus, for such a scenario we need to use a saga, instead of letting the local transactions to handle everything on their own. A managing transaction – probably in the

CheckoutService – would need to orchestrate the local transaction in CustomerService, ProductService, and OrderService. And if there would be a problem – for example a payment could not be successfully performed – a signal would be needed to be sent to ProductService to put the stocks of related products to their previous state.

4.4.5 Summary

When we talk about data persistency, keeping the microservice principle of independence takes its heavy toll in complexity. Many would prefer to have independent microservices with shared databases rather than to deal with such complications. They should then realize that as their systems grow, shared databases can become the heavy chains that bind the services together, stripping them of their true independence. In other words, they would have system that would have the common downsides of microservices while being denied reaping many of their benefits.

Using the Saga pattern requires a skilled programmer and projects that would take this direction should be certain that they have those at their disposal. However, it should be the proper way to go on projects that seek to guard their maintainability in the long run, instead of making technical debts as described in chapter 2.1.2.

4.5 Testing

In the last research problem, we will focus on the differences related to testing. In chapter 2.4 we defined different types of tests and now we will compare how they vary depending on the architectures used.

4.5.1 Unit Tests

In the interview responses we can see that unit testing praises microservices, as their well-defined scope makes them much easier to write. Similarly, responsibilities for unit tests are easier to separate, as the number of developers working on a unit is limited only to the project team working in that service.

In monoliths, the situation is not that much worse but in terms of unit tests, they still fall behind the convenience of microservices.

4.5.2 Integration Tests

Likewise, as the unit testing, integration testing favors the microservices. Reason for it is that the exposed API of each service offers convenient spot for each integration test as that is the entry point where the service communicates with other components or consumers.

Failed integration tests might also indicate that a change to the service interface has been done and that this situation may influence other services that use its API.

4.5.3 Regression Tests

The implementation convenience is similar to the previous integration tests point. However, another interesting thought among the interview responses mentions another fact (RQ3).

When only a subset of all services is changed in a release, regression tests for the other untouched services do not need to be run. This might save a lot of time to those responsible for overseeing the regression tests, as otherwise the high number of services would require much additional effort.

In theory, monoliths could also run only those regression tests that relate to components changed since the last release. That would, however, be potentially risky as the tight coupling in monoliths makes confident tracking of dependencies quite difficult.

4.5.4 End-to-end Tests

As end-to-end testing focuses on the overall functionality, with microservices it gets a bit complicated as it was mentioned in interview responses.

When administration of business requirements is split into a number of services, it is much harder to trace the error. For this reason, sophisticated monitoring system needs to be implemented. Fortunately, there is available software for monitoring the information flow of separate services side-by-side that should make this a little less of a challenge (Henke, 2020).

In monoliths, implementing a system for tracing an error is much easier as everything happens within a scope of one application. Unless 3rd party endpoints are used in the workflow.

4.5.5 Summary

When it comes to testing the logic on smaller scale as of classes of components, microservice appear as a winner. Convenience of microservices makes the life easier especially to the responsible developers of automated tests.

However, as we get on the level of service orchestration that is tested in end-to-end tests, complexity rises again, similarly as described in the research problem Infrastructure and deployment. Solving these microservice challenges require some additional expertise, especially for the DevOps team members.