This is the final episode of our series on apps architecture in which we are going to cover methods of application deployment. If you want to know more about monoliths and microservices, certainly check out the first and second chapters as well.
Whatever architecture is chosen, a set of excellent development practices may be used to optimize the application lifecycle during the release and maintenance stages. Adopting these principles improves robustness, reduces recovery time, and makes it clear how a service handles incoming requests.
Health checks, metrics, logs, tracing, and resource usage are all part of these procedures.
Health Checks
Health checks are used to show how well an application is performing. These tests determine if an application is up and running and if it is behaving as anticipated while serving incoming traffic. Health checks are usually expressed by an HTTP endpoint like /health or /status. These endpoints produce an HTTP response that indicates if the application is healthy or not.
Metrics
Metrics are required to quantify the application's performance. It is necessary to collect data on how a service handles requests in order to properly comprehend how the service works. For instance, the number of active users, requests handled, and logins. Furthermore, data on the resources that the program uses to function properly are critical: the quantity of CPU, RAM, and network throughput. Typically, a collection of metrics is delivered via an HTTP endpoint like /metrics, which comprises internal data like the number of active users, consumed CPU, network throughput, and so on.
Logs
Log aggregation offers useful information about how a service is operating at any given time. Any troubleshooting and debugging procedures revolve around it. For example, it's critical to keep track of whether a user successfully registered into a service or experienced a problem while making a payment.
A passive logging technique is usually used to gather logs from STDOUT (standard out) and STDERR (standard error). This implies that the application's output and errors are delivered to the shell. These are then gathered and saved in backend storage using a logging solution like Fluentd or Splunk.
The application, on the other hand, can transmit the logs straight to the backend storage. In this scenario, active logging is employed since the log transmission is handled directly by the program, eliminating the need for a logging tool.
There are multiple logging levels that can be attributed to an operation. Some of the most widely used are:
DEBUG - record fine-grained events of application processes.
INFO - provide coarse-grained information about an operation.
WARN - records a potential issue with the service.
ERROR - notifies an error has been encountered, however, the application is still running.
FATAL - represents a critical situation, when the application is not operational.
In addition, it is standard practice to provide a timestamp to each log line, which will precisely record when an action was performed.
Tracing
Tracing can provide a complete view of how several services are used to complete a single request. Tracing is usually implemented at the application layer using a library that allows the developer to track when a certain service is called. Individual service records are organized into spans. A trace that recreates the complete lifespan of a request is defined by a collection of spans. A trace is defined as a collection of spans that recreates the complete lifetime of a request.
Resource Consumption
The term "resource consumption" refers to the resources that a program requires to function properly. This generally refers to the amount of CPU and memory that a program consumes while running. Additionally, it is beneficial to benchmark the network throughput, or how many requests an application can handle concurrently. It is critical to be aware of resource limitations in order to keep the application up and operating 24 hours a day, seven days a week.
Maintenance
After the production release, both monolithic and microservice-based programs enter the maintenance phase. The application structure and functionality may change during this period, which is to be anticipated! An application's architecture is fluid and in continual motion, not static. This is the result of a product's organic growth driven by consumers' inputs and new technology.
When it comes to adding new features or adopting new technologies, it's a good point to prioritize extensibility above flexibility if we are thinking at the service level scale. In general, managing numerous services with well-defined and basic functionality (as in the case of microservices) is more efficient than adding more abstraction layers to accommodate new services (as in the case of monoliths). But it doesn't mean that you should ignore flexibility flaws. So, both are factors that make sense in different scenarios. In the end, the main goal is to always bring value to the users and customers.
As for the architectural changes, it is necessary to understand why a certain architecture is chosen for an application and the trade-offs involved to have a well-structured maintenance phase. The following are some of the most commonly performed operations during the maintenance period:
Split operation - relevant when a service has too many capabilities and is difficult to administer. In this case, smaller, more manageable pieces are preferable.
Merge operation - operation used to merge units that are too granular or conduct tightly interconnected functions. For example, combining two distinct log output and log format services into a single service.
Replace operation - relevant when a more efficient implementation of a service is found. For example, rewriting a Java service in Go to reduce the total execution time.
Stale operation - relevant for services that are no longer useful to the company and should be archived or deprecated. For example, services that were utilized to conduct a one-time migration.
Any of these activities will help a project last longer and stay on track. Overall, the objective is to guarantee that the application adds value to consumers while also being simple to administer for the technical team. Besides that, you can see that a project's structure is not static. It's amorphous, and it changes in response to new needs and input from customers.
Reach out to get more information.
Artyom, sr. software engineer
References: