Skip to content

Java Spring – Concepts

Open all questions about Spring

☕ Buy Me a Coffee ☕

Content:

The Saga Pattern is a design pattern used to manage distributed transactions in microservice architectures. A Saga is a sequence of transactions, each of which updates data in a single service and publishes an event or message. If any transaction in the sequence fails, the Saga calls a series of compensating transactions to undo the changes made earlier.

Key features of Saga Pattern:

Distributed transaction:
Unlike traditional transactions, which are controlled by a database management system (DBMS) and maintain ACID properties, Saga distributed transactions involve multiple steps, each of which can call different services.

Compensation transactions:
For each step that can change the state of the system, there is a corresponding compensation transaction that can undo its effects. This is necessary if subsequent steps in the Saga cannot be completed successfully.

Sequence Control:
Saga can be implemented in two variants – serial and parallel. Serial execution requires that each step be completed before the next one can begin. Parallel execution allows some steps to be executed simultaneously, but still requires that some operations depend on the results of previous ones.

Examples of using Saga Pattern:

Order processing:
When processing an order, various services may be involved – the order service, the payment service and the delivery service. If the payment does not go through, it is necessary to cancel the order and, possibly, release the reserved goods.

Travel booking:
When booking a flight, hotel and car rental as part of a single trip, each booking represents a separate Saga step. If one of the steps fails (e.g. the car cannot be booked), the other successful bookings must be cancelled to avoid unnecessary costs to the user.

Types of Saga:

Orchestrated Saga:
A central orchestrator manages the execution and coordination of each transaction and compensation transaction. In an orchestration approach, a central coordinator (called an orchestrator) manages the Saga logic, telling each service what actions to perform. The orchestrator is responsible for processing responses from services and deciding on next steps, including initiating compensation actions in the event of failures.

Advantages:
It is easier to manage and monitor the execution of Saga, since all control logic is concentrated in one place.
It's easier to ensure consistency and error recovery.

Flaws:
The orchestrator becomes a single point of failure, potentially reducing the system's resilience.
A bottleneck may occur when the system scales up due to centralized control.

Choreographed Saga:
Each service in a SAGA listens for events and performs its work autonomously by publishing events to other services. There is no centralized control component in this approach. Instead, each service participating in a Saga knows what steps to take next and when to initiate the next step or compensating action. Services communicate with each other by sending events that can be caught by other services, causing the next steps to be executed.

Advantages:
Less dependence on a single control component, which increases system fault tolerance.
A more flexible and scalable system, as new services can be added with minimal changes to other services.

Flaws:
It can be more difficult to track the progress of a Saga since there is no central point of control.
It is more difficult to ensure data consistency due to the asynchronous nature of communications.

How Saga Pattern works:
The client initiates the creation of an order.
Order Service creates an order and publishes an OrderCreatedEvent.
Payment Service handles the OrderCreatedEvent event and attempts to complete the payment.
If the payment is successful, the Payment Service publishes a PaymentCompletedEvent.
If the payment fails, the Payment Service publishes a PaymentFailedEvent.
Inventory Service processes the OrderCreatedEvent event and reserves the items.
If the reservation is successful, the Inventory Service publishes an InventoryReservedEvent.
If the reservation fails, the Inventory Service publishes an InventoryReservationFailedEvent.
Order Service processes PaymentCompletedEvent, PaymentFailedEvent, InventoryReservedEvent, InventoryReservationFailedEvent events and completes or cancels the order.

import org.springframework.stereotype.Service


@Service
class Service1 {

  
    fun performTransaction1() {
        // Logic for T1
    }

    
    fun compensateTransaction1() {
        // Compensation logic for T1
    }
    
}
import org.springframework.stereotype.Service


@Service
class SagaOrchestrator(
    private val service1: Service1,
    private val service2: Service2,
    private val service3: Service3,
    private val service4: Service4
) {

  
    fun executeSaga() {
        try {
            service1.performTransaction1()
            service2.performTransaction2()
            service3.performTransaction3()
            service4.performTransaction4()
        } catch (e: Exception) {
            service4.compensateTransaction4()
            service3.compensateTransaction3()
            service2.compensateTransaction2()
            service1.compensateTransaction1()
        }
    }
    
}

The Rollback Pattern is an approach to handling failures in distributed systems in which if one operation in a sequence of transactions fails, all the operations that have been performed are rolled back, returning the system to its original state. Unlike the Saga Pattern, which uses compensating transactions, the Rollback Pattern undoes changes using explicit rollback.

How Rollback Pattern works:

Compensatory actions:
For each operation that changes the state of the system, a compensating action is defined. This action must undo the changes made by the original operation.

Identifying failures:
The system must be able to detect when an operation has failed and initiate compensatory actions.

Isolated operations:
Operations must be independent and isolated so that compensation actions do not affect other operations.

Transactionality:
It is used in systems where it is necessary to ensure the atomicity of operations. This means that all operations within a single transaction are either executed in full or not executed at all.

State logging:
Often requires the system to keep a log of changes or states before a transaction began so that it can be reverted to a previous state.

How Rollback Pattern works:
The client initiates the creation of an order.
Order Service creates an order and publishes an OrderCreatedEvent.
Payment Service handles the OrderCreatedEvent event and attempts to complete the payment.
If the payment is successful, the Payment Service publishes a PaymentCompletedEvent.
If the payment fails, the Payment Service publishes a PaymentFailedEvent.
The Order Service handles the PaymentFailedEvent event and cancels the order by publishing an OrderCancelledEvent event.

import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional


@Service
class Service1 {

  
    @Transactional
    fun performTransaction1() {
        // Logic for T1
    }

    
    @Transactional
    fun rollbackTransaction1() {
        // Rollback logic for T1
    }
    
}

// Similar for Service3 and Service4
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional


@Service
class TransactionManager(
    private val service1: Service1,
    private val service2: Service2,
    private val service3: Service3,
    private val service4: Service4
) {

  
    @Transactional
    fun executeTransactions() {
        try {
            service1.performTransaction1()
            service2.performTransaction2()
            service3.performTransaction3()
            service4.performTransaction4()
        } catch (e: Exception) {
            service4.rollbackTransaction4()
            service3.rollbackTransaction3()
            service2.rollbackTransaction2()
            service1.rollbackTransaction1()
            throw e
        }
    }
    
}

Fairytale Pattern:

Compensating transactions:
In the event of an error, compensating transactions are performed to undo the effects of the operations already performed.

Asynchrony:
Typically used in asynchronous distributed systems, such as microservices.

Continuation of work:
Partial failures can be handled and the system continues to operate.

Rollback Pattern:

Full rollback:
In case of an error, a complete rollback of all performed operations is performed, returning the system to its original state.

Synchronicity:
More commonly used in synchronous systems and requires a global transaction.

Strict consistency:
Provides strong data consistency, but may be less flexible and less performant.

Application area:
The Rollback Pattern is typically used in the context of a single database or within a single service, where all changes can be rolled back to the initial state if an operation fails.
The Saga Pattern is applied in distributed systems and microservice architectures where transactions affect multiple services and rollback must be performed through a series of compensation operations between different services.

State Management:
In the Rollback Pattern, state is managed locally within a single system or component, and it is usually possible to use transaction management mechanisms (such as in a DBMS) to roll back to the original state.
The Saga Pattern requires coordination between multiple services, each managing its own state. Rollback in Saga is organized through compensation transactions, which must be explicitly defined and implemented for each step in the process.

Complexity and control:
The Rollback Pattern is relatively simple to implement within a single system or application that supports atomic transactions.
The Saga Pattern requires more complex coordination and error handling logic, since each Saga step may require compensating actions to be performed in other services.

Fault tolerance and scalability:
The Rollback Pattern may be limited within a single system, which reduces its applicability in scalable distributed systems.
The Saga Pattern is designed specifically for distributed and scalable systems, providing capabilities for managing complex business processes and providing fault tolerance at the architectural level.

Event Streaming concepts are fundamental to building modern distributed systems and microservice architectures. These concepts allow applications to transmit, receive, and process streams of events in real time. The main concepts and components of event streaming are:

Key concepts of event streaming:

Event:
a record of a significant change in state or action that has occurred in the system. For example, adding a new user, updating an order, or completing a transaction.
An event contains information about what happened, such as the event type, timestamp, and data associated with the event.

Event Stream:
represents a continuous sequence of events occurring in real time.
The stream can be ordered by timestamps or other criteria.

Manufacturer:
a component or service that generates events and sends them to a stream.
An example might be a service that logs events when new users are added.

Consumer:
a component or service that receives events from a stream and processes them.
An example might be an analytics system that processes and analyzes events from a stream.

Topic:
a logical grouping of events in an event streaming system.
Producers send events to a topic, and consumers subscribe to the topic to receive events.

Message Broker:
a system that manages topics and routes events from producers to consumers.
Examples of message brokers: Apache Kafka, RabbitMQ, Amazon Kinesis.

Event Processing:
includes filtering, transformation, aggregation and other operations on events in real time.
Often used to build streaming analytics applications.
Event Streaming Use Cases

Monitoring and alerts:
Event streaming is used to monitor systems in real time and send alerts when anomalies or events occur.

Real-time analytics:
Processing and analyzing event streams to obtain real-time analytics. Examples: transaction processing, website click tracking.

Microservice architectures:
Microservices can use event streaming for asynchronous communication and coordination. This helps reduce coupling between microservices and improve system scalability.

Eventual Consistency is a data consistency model used in distributed systems and databases. This model ensures that if no new updates are made to the data, all copies of the data in the system will eventually become consistent. In other words, all changes to the data will eventually be replicated to all nodes in the system.

Key aspects of eventual consistency:

Asynchronous data update:
In a distributed system, data updates can occur asynchronously. This means that after changes are made, the data will not be immediately consistent across all nodes in the system.

Final approval:
If no new updates occur, the system ensures that all nodes will eventually reach the same state.

Allowance for time inconsistency:
Temporary data inconsistency is possible and acceptable. The system may temporarily display old or inconsistent data until all updates have been replicated and applied to all nodes.

Applicability:
Eventual consistency is often used in distributed systems such as distributed databases, caching systems, and replication systems where maintaining high availability and performance is important.
Examples of systems with eventual consistency

NoSQL databases:
Many NoSQL databases such as Cassandra, DynamoDB, Couchbase, MongoDB (in replication mode) use the eventual consistency model to achieve high availability and scalability.

Caching systems:
Caching systems such as Redis (in cluster mode) can use eventual consistency to provide high performance when accessing data.

Messaging systems:
Messaging systems such as Apache Kafka can also use the eventual consistency model to provide high performance and availability.

Principles compiled by Robert Martin, author of Clean Architecture. SOLID is an acronym for five principles of object-oriented programming and design that help create flexible, scalable, and maintainable software systems.

A class should have only one reason to change, should perform only one task.

Bad example:

class UserService {
  
    fun registerUser(user: User) {
        // User registration
        println("User registered: ${user.name}")
        sendWelcomeEmail(user)
    }

    private fun sendWelcomeEmail(user: User) {
        // Sending a Welcome Email
        println("Welcome email sent to ${user.email}")
    }
    
}

What's bad about this:
The UserService class performs two tasks: registering the user and sending a welcome email. This violates the single responsibility principle, since changing the email sending logic would require changing the UserService.

Good example:

class UserService(private val emailService: EmailService) {
  
    fun registerUser(user: User) {
        // User registration
        println("User registered: ${user.name}")
        emailService.sendWelcomeEmail(user)
    }
    
}

class EmailService {
  
    fun sendWelcomeEmail(user: User) {
        // Sending a Welcome Email
        println("Welcome email sent to ${user.email}")
    }
    
}

What's good about this:
The UserService class is responsible only for registering the user, and the EmailService is responsible for sending emails. This corresponds to the single responsibility principle.

The class must be open for extension and closed for modification.

Bad example:

class DiscountService {
  
    fun applyDiscount(price: Double, customerType: String): Double {
        return if (customerType == "Regular") {
            price * 0.9
        } else if (customerType == "Premium") {
            price * 0.8
        } else {
            price
        }
    }
    
}

What's bad about this:
Adding a new type of customer would require changing the DiscountService class, which would violate the open/closed principle.

Good example:

interface DiscountPolicy {
    fun applyDiscount(price: Double): Double
}

class RegularDiscountPolicy : DiscountPolicy {
  
    override fun applyDiscount(price: Double): Double {
        return price * 0.9
    }
    
}

class PremiumDiscountPolicy : DiscountPolicy {
  
    override fun applyDiscount(price: Double): Double {
        return price * 0.8
    }
    
}

class DiscountService(private val discountPolicy: DiscountPolicy) {
  
    fun applyDiscount(price: Double): Double {
        return discountPolicy.applyDiscount(price)
    }
    
}

What's good about this:
The DiscountService class is open for extension (new discount types can be added), but closed for modification (no need to modify existing code to add new discounts).

Code designed for a base class must work correctly with any of its descendants.

Bad example:

open class Bird {
  
    open fun fly() {
        println("Bird is flying")
    }
    
}

class Penguin : Bird() {
  
    override fun fly() {
        throw UnsupportedOperationException("Penguins can't fly")
    }
    
}

What's bad about this:
The Penguin subclass violates the LSP principle because it cannot replace the Bird base class without changing the logic (the fly method is not applicable to penguins).

Good example:

open class Bird {
  
    open fun move() {
        println("Bird is moving")
    }
    
}

class Penguin : Bird() {
  
    override fun move() {
        println("Penguin is swimming")
    }
    
}

What's good about this:
The Penguin subclass replaces the Bird base class without breaking its logic. The move method can mean different behavior for different birds.

The interface should contain only the methods needed by the client.

Bad example:

interface Worker {
    fun work()
    fun eat()
}

class HumanWorker : Worker {
  
    override fun work() {
        println("Human is working")
    }

    override fun eat() {
        println("Human is eating")
    }
    
}

class RobotWorker : Worker {
  
    override fun work() {
        println("Robot is working")
    }

    override fun eat() {
        throw UnsupportedOperationException("Robots don't eat")
    }
    
}

What's bad about this:
The Worker interface includes methods that are not applicable to all its implementations. Robots must not implement the eat method.

Good example:

interface Worker {
    fun work()
}

interface Eater {
    fun eat()
}

class HumanWorker : Worker, Eater {
  
    override fun work() {
        println("Human is working")
    }

    override fun eat() {
        println("Human is eating")
    }
    
}

class RobotWorker : Worker {
  
    override fun work() {
        println("Robot is working")
    }
    
}

What's good about this:
The Worker and Eater interfaces are separated by functionality. Classes implement only those interfaces that they actually need.

High-level code works with interfaces and should not depend on their implementation.

Bad example:

class EmailService {
  
    fun sendEmail(message: String) {
        println("Sending email: $message")
    }
    
}

class NotificationService {
  
    private val emailService = EmailService()

    fun notify(message: String) {
        emailService.sendEmail(message)
    }
    
}

What's bad about this:
NotificationService is tightly dependent on the concrete EmailService class, making it difficult to modify and test.

Good example:

interface MessageService {
    fun sendMessage(message: String)
}

class EmailService : MessageService {
  
    override fun sendMessage(message: String) {
        println("Sending email: $message")
    }
    
}

class SmsService : MessageService {
  
    override fun sendMessage(message: String) {
        println("Sending SMS: $message")
    }
    
}

class NotificationService(private val messageService: MessageService) {
  
    fun notify(message: String) {
        messageService.sendMessage(message)
    }
    
}

What's good about this:
NotificationService depends on the MessageService abstraction rather than a concrete implementation. This allows for easy implementation swapping and improves code testability.

Clean Architecture is a software development concept proposed by Robert S. Martin (Uncle Bob). It separates a system into different layers with clearly defined responsibilities and dependencies, resulting in a flexible, testable, and maintainable system.

The main principles of clean architecture:

Dependencies are directed inward:
The outer layers may depend on the inner ones, but not vice versa.

Outer layer:
Includes user interface and infrastructure.

Inner layer:
Includes business logic and entities.

Input and output ports:
Define interfaces for interaction between layers.

Project structure:
Example of project structure using clean architecture in Spring Boot:

src/main/java/com/example/demo
├── DemoApplication.java
├── config
│   └── WebConfig.java
├── controller
│   └── UserController.java
├── entity
│   └── User.java
├── repository
│   └── UserRepository.java
├── service
│   └── UserService.java
├── usecase
│   └── GetUserUseCase.java
└── web
    ├── UserRequest.java
    └── UserResponse.java

When developing a RESTful web service, there are several key parameters and aspects to consider to ensure compliance with REST principles and to achieve maximum efficiency, usability, and scalability. Here are the key parameters to consider:

Resources:
URL (Uniform Resource Locator): Resources are identified by unique URLs. For example, /users for users or /products for products.
Naming: Resources are usually named in the plural (e.g. /users, /orders).

HTTP Methods:
GET: To get a resource or list of resources.
POST: To create a new resource.
PUT: To update an existing resource.
DELETE: To delete a resource.
PATCH: To partially update a resource.

HTTP Status Codes:
200 OK: Successful request.
201 Created: Successful creation of a resource.
204 No Content: The request was successful and no content was returned.
400 Bad Request: Incorrect request.
401 Unauthorized: Authentication required.
403 Forbidden: Access denied.
404 Not Found: Resource not found.
500 Internal Server Error: Internal Server Error.

HTTP Headers:
Content-Type: Specifies the content type (e.g. application/json).
Accept: Specifies what content format the client expects.
Authorization: Used to transmit authentication information.

Data Formats:
JSON (JavaScript Object Notation): A widely used format for data transfer.
XML (eXtensible Markup Language): Used in some systems for data transmission.

Error Handling:
Error messages should be informative and provide enough information to understand the problem.
Structured error messages in JSON or XML format.

Caching:
Using caching headers (Cache-Control, ETag, Last-Modified) to improve performance and reduce server load.

Authentication and Authorization:
Basic Auth: A simple authentication method using username and password.
Token-based Auth (e.g., JWT): A more secure and flexible authentication method.
OAuth: An authorization protocol that allows third-party applications to gain limited access to resources.

API Versioning:
Including the version in the URL (e.g. /v1/users).
Using headers to indicate API version.

Documentation:
Documentation should be complete and clear, including descriptions of all endpoints, parameters, data formats, error statuses, and sample requests/responses.
Documentation tools: Swagger/OpenAPI.

REST (Representational State Transfer) is an architectural style for interacting with web services that uses standard HTTP methods to perform operations on resources. In REST, the basic HTTP methods are used to work with resources (such as data) and determine what action should be performed.

Here are the main HTTP methods used in the REST API:

Purpose:
Getting data (reading a resource).

Description:
This method is used to request data from the server. Requests with the GET method do not change the state of the server and are considered idempotent (i.e., a repeated request will not result in changes).

Example of use:

GET /users

Returns a list of all users.

GET /users/1

Returns data for user with ID 1.

Purpose:
Creating a new resource.

Description:
The POST method is used to send data to the server to create a new resource. This method is not idempotent – repeated requests may create a new resource.

Example of use:

POST /users
Content-Type: application/json

{
  "name": "Alice",
  "email": "[email protected]"
}

Creates a new user named "Alice".

Purpose:
Complete resource update.

Description:
The PUT method is used to replace an existing resource with new data. If a resource with the specified identifier exists, it will be updated, if not, it can be created (depending on the implementation). The method is idempotent – re-executing the request will not change the result.

Example of use:

PUT /users/1
Content-Type: application/json

{
  "name": "Bob",
  "email": "[email protected]"
}

Updates the data of the user with ID 1, replacing all fields with new values.

Purpose:
Partial resource update.

Description:
The PATCH method is used to update a partial resource. Unlike PUT, which updates the entire resource, PATCH updates only the fields that were passed in the request. This method can also be idempotent if implemented appropriately.

Example of use:

PATCH /users/1
Content-Type: application/json

{
  "email": "[email protected]"
}

Updates only the email field for user with ID 1.

Purpose:
Removing a resource.

Description:
The DELETE method is used to delete a resource from the server. If the resource is deleted successfully, the server should return a success status (e.g. 200 OK or 204 No Content). This method is also idempotent – re-executing the request will not change the result.

Example of use:

DELETE /users/1

Deletes the user with ID 1.

Purpose:
Request allowed methods for a resource.

Description:
The OPTIONS method is used to request available HTTP methods for a specific resource or the entire service. The response contains information about which methods are allowed on the server for this resource. Often used to check for CORS (Cross-Origin Resource Sharing) support.

Example of use:

OPTIONS /users

Returns methods that can be performed on /users (e.g. GET, POST, etc.).

Purpose:
Getting response headers without body.

Description:
The HEAD method works the same as GET, except that only the headers are returned in the response and there is no response body. This method is useful for checking whether a resource exists or for getting metadata such as size or last modified data.

Example of use:

HEAD /users/1

Returns the headers for the request of the user with ID 1

Purpose:
Network path diagnostics.

Description:
This method is used to test and debug a network connection. The server must return the request back in the form in which it was received. It is used to diagnose routes between the client and the server.

Example of use:

TRACE /users

Purpose:
Setting up a tunnel for secure communication.

Description:
This method is used to create a tunnel through a server, usually for a secure connection using SSL/TLS. In practice, this method is most often used when implementing proxy servers.

Example of use:

CONNECT server.example.com:443 HTTP/1.1

Idempotency and safety of methods:

Idempotent methods:
These methods can be executed multiple times with the same result as if they were executed once. Idempotent methods include:
GET
PUT
DELETE
HEAD
OPTIONS

Non-idempotent methods:
Executing these methods multiple times may produce different results. For example, each time a POST is executed, a new resource may be created.
POST
PATCH

Safe methods:
Methods that do not change the state of the resource on the server are considered safe. These are:
GET
HEAD
OPTIONS
TRACE

The Spring MVC (Model-View-Controller) architecture uses several key components to manage data, views, and their interactions. Among them, Model, ModelAndView, and ViewResolver play an important role. Let's look at each of these concepts in more detail.

Model:
An interface that is used to pass data from the controller to the view. It allows you to add attributes that will be available in the view template. Model is typically used in controller methods. In this example, the greeting method adds a message attribute to the Model, which will then be available in the view named greeting.

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping


@Controller
class MyController {
  
    @GetMapping("/greeting")
    fun greeting(model: Model): String {
        model.addAttribute("message", "Hello, World!")
        return "greeting"
    }
    
}

ModelAndView:
A class that combines a model and a view. It is used to pass both data and view information in a single object. This is useful when you need to return both a model and a view from a controller. In this example, the welcome method creates a ModelAndView instance, sets the view name to welcome, and adds the message attribute.

import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.servlet.ModelAndView


@Controller
class MyController {
  
    @GetMapping("/welcome")
    fun welcome(): ModelAndView {
        val modelAndView = ModelAndView("welcome")
        modelAndView.addObject("message", "Welcome to Spring MVC!")
        return modelAndView
    }
    
}

ViewResolver:
An interface that defines a view object based on its name. It is used to map logical view names to specific views. Depending on the settings, ViewResolver can look for views in the file system, in a classpath, or use other mechanisms. This configuration class configures InternalResourceViewResolver, which looks for JSP files in the specified directory.

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.view.InternalResourceViewResolver


@Configuration
class WebConfig : WebMvcConfigurer {
  
    @Bean
    fun viewResolver(): InternalResourceViewResolver {
        val resolver = InternalResourceViewResolver()
        resolver.setPrefix("/WEB-INF/views/")
        resolver.setSuffix(".jsp")
        return resolver
    }
    
}

Inversion of Control (IoC):
a programming principle in which the management of objects and their dependencies is delegated to an external container or framework. This means that instead of creating and managing dependencies manually, the programmer allows the container to manage these tasks. IoC helps to decouple different parts of a system and reduce the dependencies between them, making the system more flexible and easier to test.

IoC principles:
The container manages the life cycle of objects: The container is responsible for creating, initializing, and destroying objects.
The container manages object dependencies: The container injects the required dependencies into objects.

Dependency Injection (DI):
a process by which the Spring container automatically supplies dependencies (objects that your class depends on) into an object to minimize hard dependencies and improve the testability and maintainability of your code.

Basic concepts and types of DI in Spring:

IoC Container (Inversion of Control Container):
A container that manages the lifecycle of beans (components) and their dependencies. In Spring, the main containers are ApplicationContext and BeanFactory.

Beans:
These are objects that are managed by the Spring container. Beans are created, wired, and managed by the container based on metadata configuration.

Annotations:
Annotations are used to tell the Spring container how to inject dependencies. The main annotations include @Component, @Service, @Repository, @Controller, @Autowired, @Qualifier, and @Inject.

Types of Dependency Injection:

Constructor Injection:
Dependencies are passed through the class constructor.
The preferred method of DI, as it makes dependencies explicitly visible and promotes object immutability.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class UserService {

  
    private final UserRepository userRepository;

  
    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    
    public User findUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service


@Service
class UserService @Autowired constructor(
    private val userRepository: UserRepository
) {
  
    fun findUserById(id: Long): User? {
        return userRepository.findById(id).orElse(null)
    }
    
}

Setter Injection:
Dependencies are passed through setters or other class methods.
Useful for optional dependencies and allows easy change of dependencies after object creation.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

  
    private UserRepository userRepository;

  
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    
    public User findUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service


@Service
class UserService {

  
    private lateinit var userRepository: UserRepository

  
    @Autowired
    fun setUserRepository(userRepository: UserRepository) {
        this.userRepository = userRepository
    }

    
    fun findUserById(id: Long): User? {
        return userRepository.findById(id).orElse(null)
    }
    
}

Field Injection:
Dependencies are passed directly to class fields.
An easy implementation method, but less preferred due to its shortcomings in testability and manageability.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class UserService {

  
    @Autowired
    private UserRepository userRepository;

  
    public User findUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service


@Service
class UserService {

  
    @Autowired
    private lateinit var userRepository: UserRepository

  
    fun findUserById(id: Long): User? {
        return userRepository.findById(id).orElse(null)
    }
    
}

Spring configuration:

Using annotations (Java-based Configuration):

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {}

Using XML configuration:

<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <context:component-scan base-package="com.example"/>
    <bean id="userService" class="com.example.UserService">
        <property name="userRepository" ref="userRepository"/>
    </bean>
    <bean id="userRepository" class="com.example.UserRepository"/>
</beans>

Domain-Driven Design (DDD) is a software development methodology proposed by Eric Evans in his book “Domain-Driven Design: Tackling Complexity in the Heart of Software”. DDD focuses on creating software systems that accurately reflect complex business domains.

Key concepts of Domain-Driven Design:

Domain Models:
A domain model is an abstraction of the real world that includes the main entities, their attributes, and relationships. The domain model is developed in close collaboration with subject matter experts.

Terms and Conditions (Ubiquitous Language):
Conditions and constraints are a common language used by all project participants, including developers and subject matter experts. This language helps eliminate ambiguities and improve communication.

Bounded Contexts:
A constraint context is a boundary within which certain terms and concepts have clear meaning. It helps manage complexity by dividing a system into logical parts.

Entities:
Entities are objects that have a unique identifier and are defined by their identity rather than by attributes.

Value Objects:
Value objects are objects that are defined by their attributes and do not have a unique identifier. They are immutable and reusable.

Aggregates:
Aggregates are groups of related entities and value objects that are treated as a single unit. Aggregates ensure data integrity and manage changes through the Aggregate Root.

Services:
Services are operations or actions that do not belong to any particular entity or value object, but are important to the domain.

Repositories:
Repositories provide interfaces for accessing entities and aggregates from a data store, abstracting away the implementation details of the store.

Modules:
Modules help organize domain objects and their interactions by dividing the system into logical parts.

CQRS (Command Query Responsibility Separation) is an architectural pattern that separates read (query) and write (command) operations into separate models. This allows each operation to be optimized separately, improving the performance, scalability, and flexibility of the system.

The main ideas of CQRS:

Separation of models:

Command Model:
processes commands, changes system state, and applies business logic.

Query Model:
processes requests to read data, optimized to perform fast and efficient queries.

Commands and queries:

Command:
an operation that changes the state of the system (e.g. creating an order, updating a user profile). Commands are usually write operations.

Query:
an operation that retrieves data from a system (e.g., retrieving a list of orders, retrieving a user profile). Queries are typically read operations.

Event Sourcing is an architectural pattern in which the state of a system is modeled as a sequence of events. Instead of storing the current state of an object, the system stores all the events that led to that state. This allows the current state of the system to be reproduced by reapplying all the events.

Key ideas of Event Sourcing:

Events as a source of truth:
All changes in the system state are recorded as events. Each event describes a change in the system state at a specific point in time.

Reproducing the state:
The current state of an object can be obtained by sequentially applying all events associated with that object.

Immutability of events:
Events are immutable and cannot be changed once recorded. This provides a reliable and accurate log of system changes.

Circuit Breaker is a design pattern used to improve the resilience and reliability of distributed systems, especially microservice architectures. The goal of this pattern is to prevent repeated attempts to perform an operation that is likely to fail and to quickly identify failures to minimize their impact on the system.

The main ideas of Circuit Breaker:

Preventing Cascading Failures:
When one of the system's services fails to respond or works incorrectly, attempts to interact with it can cause the entire system to slow down or even crash. Circuit Breaker helps prevent such cascading failures.

Three states:

Closed:
Requests pass through the Circuit Breaker to the target service. If the number of errors exceeds a specified threshold, the Circuit Breaker moves to the Open state.

Open:
Requests fail immediately without reaching the target service. After a certain time (timeout), Circuit Breaker enters the Half-Open state.

Half-Open:
A number of requests are allowed to pass to the target service to check if it is working. If the requests are successful, the Circuit Breaker returns to the Closed state. If more errors occur, the Circuit Breaker returns to the Open state.

Mechanism for returning to normal operation:
If the target service starts working normally, Circuit Breaker automatically returns to the Closed state, allowing requests to pass again.

Example using the Resilience4j library:
Resilience4j is a popular library for implementing resilience patterns, including Circuit Breaker, in Java applications.

dependencies {
    implementation 'io.github.resilience4j:resilience4j-spring-boot2:1.7.1'
}
resilience4j.circuitbreaker:
  instances:
    myService:
      registerHealthIndicator: true
      slidingWindowSize: 10
      failureRateThreshold: 50
      waitDurationInOpenState: 10000
      permittedNumberOfCallsInHalfOpenState: 3
import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate


@Service
class MyService(private val restTemplate: RestTemplate) {

  
    @CircuitBreaker(name = "myService", fallbackMethod = "fallback")
    fun callExternalService(): String {
        return restTemplate.getForObject("http://example.com/api", String::class.java) ?: "No response"
    }

    
    fun fallback(throwable: Throwable): String {
        return "Fallback response"
    }

}

@CircuitBreaker:
annotation is used to indicate the method to which the Circuit Breaker is applied.

name = “myService”:
points to the Circuit Breaker configuration specified in application.yml.

fallbackMethod = “fallback”:
specifies a method that will be called if the Circuit Breaker is triggered.

JSON-RPC (Remote Procedure Call) is a remote procedure call protocol used to make method calls on a remote server. It uses JSON (JavaScript Object Notation) to encode data and exchange messages between the client and server. JSON-RPC is a lightweight and simple protocol that supports both one-way notifications and two-way calls and responses.

Key features of JSON-RPC:

JSON format:
Messages are encoded in JSON format, making them easy to understand and process.

Requests and answers:
Requests contain the name of the method to be called and the parameters for that method.
Responses contain the result of the method call or error information if the call failed.

Notification support:
Notifications are one-way messages that do not require a response from the server.

Simplicity and lightness:
The JSON-RPC protocol is minimalistic and does not contain complex structures, which makes it easy to implement and use.

Example of using JSON-RPC in a Spring Boot application:

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.googlecode.jsonrpc4j:jsonrpc4j:1.5.0")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
import com.googlecode.jsonrpc4j.JsonRpcService


@JsonRpcService("/api")
interface CalculatorService {
    fun subtract(a: Int, b: Int): Int
}
import org.springframework.stereotype.Service


@Service
class CalculatorServiceImpl : CalculatorService {
  
    override fun subtract(a: Int, b: Int): Int {
        return a - b
    }
    
}
import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration


@Configuration
class JsonRpcConfig {

  
    @Bean
    fun autoJsonRpcServiceImplExporter(): AutoJsonRpcServiceImplExporter {
        return AutoJsonRpcServiceImplExporter()
    }

}
import com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@RestController
@RequestMapping("/api")
class CalculatorController(
  private val calculatorService: CalculatorService
) : CalculatorService by calculatorService

The Spring MVC (Model-View-Controller) architecture is based on the well-known MVC design pattern, which divides an application into three main components: model, view, and controller. This pattern helps separate business logic, presentation logic, and control logic, making the application more modular, maintainable, and extensible.

The main components of the Spring MVC architecture are:

Model:
The model represents the application data. It contains the business logic and interacts with the database to retrieve or save data.
In Spring MVC, the model is typically represented by Java objects (POJOs) that are handled by services and repositories.

View:
The view is responsible for displaying data to the user. It generates the user interface based on the model data.
In Spring MVC, views can be represented by various technologies such as JSP, Thymeleaf, Freemarker and others.

Controller:
The controller accepts incoming requests from the client, processes them and returns the appropriate view.
Controllers in Spring MVC are typically annotated with @Controller and use methods annotated with @RequestMapping, @GetMapping, @PostMapping, etc. to map requests.

The main components of Spring MVC are:

DispatcherServlet:
The core component of Spring MVC, which acts as a front controller. It accepts all incoming HTTP requests, routes them to the appropriate handlers, and returns appropriate HTTP responses.

Handler Mapping:
is responsible for determining which controller and method should handle an incoming request. It uses URL mapping and annotations to match requests to controller methods.

Controller:
process requests, execute application logic, and return model data to be used in the view.

View Resolver:
defines which view should be used to display the data. It translates the logical name of the view into the physical location of the view, for example, it translates the name of the view into the path to a JSP file.

Model and View:
an object that contains model data and information about the view that should be used to display the data.

Request processing flow in Spring MVC:

Receiving a request:
DispatcherServlet receives an HTTP request.

Controller definition:
Handler Mapping determines which controller and method should handle the request.

Processing request:
The controller processes the request, executes business logic, and requests data from the model.

Forming a response:
The controller returns a ModelAndView object or model data along with the logical name of the view.

Definition of a view:
The View Resolver determines which view should be used based on the logical name of the view.

Rendering the view:
The view is rendered using the model data and sent back to the client as an HTTP response.

In a typical Spring application, layers are divided into several categories: controllers, services, repositories, and models. These layers help structure the code and improve its maintainability. Here are examples of each layer:

Model:
A model represents data and its structure. In Spring JPA, these are often entities that reflect database tables.

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id


@Entity
data class User(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val username: String,
    val password: String,
    val role: String
)

Repository:
The repository is responsible for accessing data and performing CRUD (create, read, update, delete) operations.

import org.springframework.data.jpa.repository.JpaRepository


interface UserRepository : JpaRepository<User, Long> {
  
    fun findByUsername(username: String): User?
    
}

Service:
The service contains the business logic of the application. It processes data received from repositories and passes it to controllers.

import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service


@Service
class UserService(
    private val userRepository: UserRepository,
    private val passwordEncoder: PasswordEncoder
) {

  
    fun findUserByUsername(username: String): User? {
        return userRepository.findByUsername(username)
    }

    
    fun registerUser(username: String, password: String, role: String): User {
        val encodedPassword = passwordEncoder.encode(password)
        val user = User(username = username, password = encodedPassword, role = role)
        return userRepository.save(user)
    }

}

Controller:
The controller processes HTTP requests and returns responses. It uses services to execute business logic and prepare data for views.

import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping


@Controller
class UserController(
  private val userService: UserService
) {

  
    @GetMapping("/register")
    fun showRegistrationForm(model: Model): String {
        model.addAttribute("user", User())
        return "register"
    }

    
    @PostMapping("/register")
    fun registerUser(@ModelAttribute user: User): String {
        userService.registerUser(user.username, user.password, "ROLE_USER")
        return "redirect:/login"
    }

}

Security Configuration:
Security configuration is responsible for setting up access and authorization.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain


@Configuration
@EnableWebSecurity
class SecurityConfig(
  private val userDetailsService: UserDetailsService
) {

  
    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http
            .authorizeRequests { requests ->
                requests
                    .antMatchers("/", "/home", "/register").permitAll()
                    .anyRequest().authenticated()
            }
            .formLogin { form ->
                form
                    .loginPage("/login")
                    .permitAll()
            }
            .logout { logout ->
                logout.permitAll()
            }
        return http.build()
    }

    
    @Bean
    fun passwordEncoder(): PasswordEncoder {
        return BCryptPasswordEncoder()
    }

}

Views:
Views display data to the user. They are often created using templating engines such as Thymeleaf.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home Page</title>
</head>
<body>
    <h1>Welcome</h1>
    <div th:if="${#httpServletRequest.remoteUser != null}">
        <p>Welcome, <span th:text="${#httpServletRequest.remoteUser}">User</span>!</p>
        <a href="/logout">Logout</a>
    </div>
    <div th:if="${#httpServletRequest.remoteUser == null}">
        <a href="/login">Login</a> | <a href="/register">Register</a>
    </div>
</body>
</html>

Main class:

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication


@SpringBootApplication
class DemoApplication


fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

Dividing an application into modules and folders helps to structure the code, improves readability and maintainability. Spring applications typically use structuring by layers and functional modules. Here are some approaches to organizing the project structure:

Structuring by layers:
The most common approach is to split into layers: controllers, services, repositories and models.

src/main/kotlin/com/example/demo
├── controller
│   ├── HomeController.kt
│   ├── UserController.kt
├── service
│   ├── UserService.kt
├── repository
│   ├── UserRepository.kt
├── model
│   ├── User.kt
├── security
│   ├── SecurityConfig.kt
└── DemoApplication.kt

Structuring by functional modules:
This approach organizes code into functional areas, which is useful for large projects with many different functional areas.

src/main/kotlin/com/example/demo
├── user
│   ├── controller
│   │   └── UserController.kt
│   ├── service
│   │   └── UserService.kt
│   ├── repository
│   │   └── UserRepository.kt
│   └── model
│       └── User.kt
├── home
│   └── controller
│       └── HomeController.kt
├── security
│   └── SecurityConfig.kt
└── DemoApplication.kt

Structuring by technical layers:
This approach groups classes into technical layers, which is especially useful for more complex architectures such as multi-layer or microservice architectures.

src/main/kotlin/com/example/demo
├── config
│   ├── SecurityConfig.kt
├── controller
│   ├── HomeController.kt
│   ├── UserController.kt
├── dto
│   ├── UserDto.kt
├── exception
│   ├── CustomException.kt
├── model
│   ├── User.kt
├── repository
│   ├── UserRepository.kt
├── service
│   ├── UserService.kt
├── util
│   ├── PasswordEncoderUtil.kt
└── DemoApplication.kt

There are two main approaches to configuration and dependency management in the Spring Framework: using annotations and using XML configuration. Both approaches have their advantages and disadvantages, and the choice between them depends on the specific requirements of the project and the preferences of the developer.

Annotations in Spring:
Annotations in Spring allow you to configure and manage dependencies directly in your code, making configuration more readable and maintainable.

XML configuration in Spring:
XML configuration allows you to describe beans and their dependencies in a separate XML file, separating the configuration from the code. This makes the configuration more declarative and easier to change without changing the code.

The main elements of XML configuration are:

<bean>:
Defines a bean and its properties.

<property>:
Defines the properties of a bean.

<constructor-arg>:
Defines the arguments of the bean constructor.

context:component-scan:
Scans the specified packages for annotated classes.

Example of using XML configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="com.example.demo"/>
    <bean id="userService" class="com.example.demo.service.UserService"/>
    <bean id="userController" class="com.example.demo.controller.UserController">
        <property name="userService" ref="userService"/>
    </bean>
</beans>

REST, RPC, GraphQL, and SOAP are different architectural styles and protocols for building web services. Let's look at the main differences between them.

REST (Representational State Transfer):

Pros:
Simplicity: Uses standard HTTP methods, making it easy to implement and understand.
Caching: Support for HTTP-level caching improves performance.
Scalability: Easily scalable due to resource independence.
Flexibility: Can use various data formats such as JSON, XML, YAML, etc.
Widespread adoption: Supported by most modern web applications and frameworks.

Cons:
Limited functionality: Does not support complex operations compared to other protocols.
Data redundancy: May transmit more data than the client requires.
Safety: Requires additional security measures such as OAuth.

RPC (Remote Procedure Call):

Pros:
Call Transparency: Ease of use as remote calls appear as local ones.
Convenience: Suitable for performing complex operations with a single call.
Efficiency: Data is transferred only when the method is called, which minimizes redundancy.

Cons:
Close connection: The client and server are tightly coupled, making API changes difficult.
Lack of standards: There is no single standard for implementation, which may cause incompatibility between different systems.
Scalability: Less scalable than REST due to the need to maintain session state.

GraphQL:

Pros:
Flexibility: Clients can request only the data they need.
Single point of entry: All requests go through a single endpoint, simplifying the architecture.
Efficiency: Fewer requests as all required data can be retrieved in one request.
Typification: Strong typing makes it easy to document and validate queries.

Cons:
Complexity: More complex to implement and maintain than REST.
Server overload: May cause server overload if the client requests too much data.
Tools: Special tools and libraries are required to work with GraphQL.

SOAP (Simple Object Access Protocol):

Pros:
Reliability: Supports WS-Security, WS-ReliableMessaging and other extensions to ensure security and reliability.
Standard: It has strict standards and specifications, which facilitates integration between different systems.
Functionality: Supports complex operations and transactions.
Cross-platform: Well supported in enterprise environments and often used in integration solutions.

Cons:
Complexity: More complex to implement and understand than REST.
Redundancy: Uses XML to transfer data, resulting in larger data volume than JSON.
Performance: Slower than REST due to the use of XML and additional overhead.

The main components of Spring are:

Spring Framework:
A core framework that includes an inversion of control (IoC) container, aspect-oriented programming (AOP), transactions, and other core features.

Spring Boot:
Makes it easy to create standalone Spring applications with minimal configuration. Includes autoconfiguration, built-in web servers, and many starters.

Web Development:

Spring MVC:
A framework for developing web applications with templating and support for RESTful web services.

Spring WebFlux:
A reactive web framework for building reactive web applications and microservices.

Thymeleaf:
Server template for creating HTML pages.

Angular, React, Vue.JS, TypeScript:
Frontend frameworks for building client applications that can be integrated with Spring Boot via RESTful API or GraphQL.

Access to data:

Spring Data JPA:
Integration with JPA to simplify working with databases via ORM.

Spring Data MongoDB:
MongoDB support.

Spring Data Redis:
Redis support.

Spring JDBC:
Easy interaction with databases via JDBC.

Spring Data REST:
Automatic creation of REST API for data repositories.

Integration and messaging:

Spring Integration:
A framework for building integration solutions based on Enterprise Integration Patterns.

Spring Batch:
Support for batch data processing.

Spring AMQP:
RabbitMQ support.

Skip Kafka:
Apache Kafka support for messaging.
Safety

Spring Security:
Framework for authentication and authorization of applications.

Spring Security OAuth2:
Support for OAuth2 and OpenID Connect.

Microservices and Cloud:

Spring Cloud:
A set of tools for developing microservices, including configuration, service discovery, routing, and more.

Spring Cloud Netflix:
Integration with Netflix OSS libraries such as Eureka, Hystrix, and Zuul.

Spring Cloud Gateway:
A reactive API gateway for routing and processing requests.

Spring Cloud Config:
External centralized configuration for microservices.

Docker:
Containerize applications to simplify deployment and management.

Kubernetes:
Container orchestration for scalability and reliability.

CI/CD tools:
Jenkins, GitLab CI, CircleCI and others.

DevOps and Monitoring:

Spring Boot Actuator:
Metrics, monitoring and application management.

Spring Cloud Sleuth:
Distributed systems tracing.

Spring Boot Admin:
An administrative interface for managing and monitoring Spring Boot applications.

API and documentation:

Swagger/OpenAPI:
Tools for API documentation and testing.

Spring REST Docs:
Generate documentation for REST API based on tests.

Monitoring and management:

Spring Boot Actuator:
A set of tools for monitoring and managing Spring Boot applications.

Prometheus, Grafana, Splunk, Dynatrace:
Monitoring and visualization of metrics.

ELK Stack (Elasticsearch, Logstash, Kibana):
Collection, storage and analysis of logs.

Other utilities and tools:

MapStruct:
Tool for mapping DTOs and entities.

Lombok:
A library for reducing boilerplate code (e.g. getters, setters, constructors).

Spring Cache:
Caching abstraction with support for various providers (EhCache, Hazelcast, Redis).

Testing:

Spring Test:
Support for unit and integration testing with JUnit and TestNG.

Spring Boot Test:
Support for testing Spring Boot applications, including web layer and data access testing.

Mockito:
A framework for creating mock objects and writing tests.

Selenium, Cucumber

Spring and Java EE (now Jakarta EE) are two different platforms for building enterprise applications in Java. Both platforms offer different tools and frameworks for development, but they have their own characteristics, advantages, and disadvantages.

The main differences between Spring and Java EE (Jakarta EE):

Architecture and approaches:

Spring:
It is a framework that provides comprehensive solutions for application development. It is modular and allows you to use only the necessary components.
Uses a container for inversion of control (IoC) and dependency injection (DI).
Lightweight and flexible, allows easy integration with other frameworks and libraries.
Does not require an application server, you can use built-in servers (eg Tomcat, Jetty).

Java EE (Jakarta EE):
It is a platform standard for developing enterprise applications in the Java language, providing a set of specifications and APIs.
Based on application servers such as WildFly, GlassFish, WebLogic, and JBoss, which provide all the necessary components.
Standardized and managed by a consortium (formerly Oracle, now the Eclipse Foundation).
More often used in large corporate applications with high requirements for reliability and security.

Containers and Dependency Injection:

Spring:
Based on an IoC container that manages the lifecycle of objects and their dependencies.
Supports multiple configuration methods: XML, annotations, JavaConfig.

Java EE (Jakarta EE):
Uses containers to manage the life cycle of components (EJB, servlets).
Dependency injection is performed using CDI (Contexts and Dependency Injection).

Components and modules:

Spring:
Includes many projects and modules such as Spring MVC, Spring Data, Spring Security, Spring Boot, Spring Cloud and more.
Spring Boot makes it much easier to create and configure applications by providing ready-made templates and auto-configuration.

Java EE (Jakarta EE):
Consists of many specifications such as Servlet, JSP, JSF, JPA, EJB, JAX-RS, JMS and others.
All specifications are interconnected and standardized, ensuring compatibility between different implementations.

Community and Support:

Spring:
Active community and strong support from Pivotal (now part of VMware).
Rapid implementation of new technologies and updates.

Java EE (Jakarta EE):
Managed by a community under the auspices of the Eclipse Foundation (formerly Oracle).
Updates and new versions are released less frequently, but the specifications are carefully tested and standardized.

Tools and integration:

Spring:
Easily integrates with various tools and libraries such as Hibernate, MyBatis, Apache Kafka and others.
Spring Boot provides built-in servers to make development and testing easier.

Java EE (Jakarta EE):
Requires the use of full-featured application servers, which can be more complex and difficult to initially set up.
Integrates well with other Java specifications and third-party libraries.

Aspect-oriented programming (AOP) is a programming paradigm that allows for the separation of cross-cutting aspects of an application from its core business logic. The primary goal of AOP is to improve the modularity of an application by providing a way to isolate and reuse code that crosses multiple modules or classes, such as logging, transaction management, security, and others.

Key concepts of AOP:

Aspect:
A module containing cross-cutting functionality. An aspect may contain one or more pointcuts and advice.

Advice:
The action performed by the aspect. Types of advice include:
Before: executed before the method is executed.
After: executed after the method is executed.
AfterReturning: executed after the method completes successfully.
AfterThrowing: executed after a method has thrown an exception.
Around: executed before and after the method is executed.

Pointcut:
A condition that determines which methods and objects the advice will be applied to. A pointcut is used to determine where the advice should be executed.

Join Point:
A specific location in program execution, such as a method call or exception handling, where advice can be applied.

Weaving (Weaving machine):
The process of associating aspects with target objects to create proxy objects. In Spring AOP, the loom happens at runtime.

Processing a request and returning a response in Spring MVC (Model-View-Controller) occurs through a chain of interactions between several components. The main stages of request processing in Spring include:

Receiving a request:
A web server (e.g. Tomcat) receives an HTTP request.

Submitting a request:
The web server passes the request to the Spring DispatcherServlet.

DispatcherServlet request processing:

Processing through filters:
The request can pass through filters (Filter) for preliminary processing.

Controller definition:
DispatcherServlet uses HandlerMapping to determine the appropriate controller to handle the request.

Calling a controller method:
The selected controller method processes the request and returns the result.

Processing via interceptors:
The request and response can pass through interceptors (HandlerInterceptor) for additional processing.

Forming a response:
The DispatcherServlet passes the result to the ViewResolver to render the view (if necessary) or returns the data directly (e.g. JSON).

Sending a response to the client:
The web server sends the generated HTTP response to the client.

Detailed request processing process:

Receiving a request:
When a client (such as a browser or Postman) sends an HTTP request to a server, the request goes to a web server (such as Apache Tomcat).

Passing a request to DispatcherServlet:
The web server passes the request to the Spring servlet dispatcher (DispatcherServlet). DispatcherServlet is the Front Controller for all incoming requests in Spring MVC.

DispatcherServlet request processing:

Processing through filters:
Filters can be used to perform pre-processing of requests such as logging, authentication, authorization, etc.

Controller definition:
DispatcherServlet uses one or more HandlerMapping to determine which controller and which controller method should handle the request. HandlerMapping determines controllers based on URL patterns, annotations, and other criteria.

Calling a controller method:
After defining a controller, DispatcherServlet calls the appropriate controller method. The controller method processes the request, executes the necessary business logic, and generates the result.

Processing via interceptors:
Interceptors (HandlerInterceptor) can be used to perform additional processing before and after a controller method is called. Interceptors can modify requests and responses, and perform additional checks and logging.

Forming a response:

Returning data directly:
If a controller method is annotated with @ResponseBody or a controller is annotated with @RestController, the returned data (such as an object or list of objects) is automatically converted to JSON or XML format using HttpMessageConverter.

Rendering the view:
If a controller method returns a view name (such as an HTML template), the DispatcherServlet uses the ViewResolver to render the view. The ViewResolver determines which template to use to generate the HTML response.

Sending a response to the client:
After generating a response (whether it's JSON, XML, or HTML), the web server sends an HTTP response back to the client.

A high-performance asynchronous reactive programming library for the JVM, developed by Pivotal (now VMware) as part of the Spring ecosystem. Reactor is the primary implementation of the Reactive Streams specification, and it provides powerful tools for creating asynchronous, event-driven programs and systems.

Key features of Project Reactor:

Reactive types:

Mono:
Represents an asynchronous stream that can contain 0 or 1 element. Analogous to Future or CompletableFuture, but with additional reactive operators.

Flux:
Represents an asynchronous stream that can contain 0, 1 or more elements (data stream).

Rich set of operators:
Support for multiple operators for working with data, such as filtering, transformation, joining, aggregation and more.

Asynchrony and non-blocking I/O:
Reactor supports asynchronous execution and non-blocking I/O, making it ideal for building high-performance and scalable applications.

Support for Reactive Streams specification:
Reactor complies with the Reactive Streams specification, ensuring compatibility with other libraries and frameworks that support Reactive Streams.

Integration with the Spring ecosystem:
Reactor is tightly integrated with the Spring Framework and Spring Boot, providing reactive capabilities for building web applications, interacting with databases, and more.

import reactor.core.publisher.Mono


fun main() {
    val mono: Mono<String> = Mono.just("Hello, Reactor!")
    mono.subscribe(
        { value -> println(value) },  // onNext
        { error -> println("Error: $error") },  // onError
        { println("Completed") }  // onComplete
    )
}
import reactor.core.publisher.Flux


fun main() {
    val flux: Flux<String> = Flux.just("Hello", "Reactor", "World")
    flux.subscribe(
        { value -> println(value) },  // onNext
        { error -> println("Error: $error") },  // onError
        { println("Completed") }  // onComplete
    )
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono


@RestController
class GreetingController {
  
    @GetMapping("/hello")
    fun hello(): Mono<String> {
        return Mono.just("Hello, Spring WebFlux!")
    }
    
    @GetMapping("/greetings")
    fun greetings(): Flux<String> {
        return Flux.just("Hello", "Hi", "Hey", "Hola")
    }
    
}

Mono and Flux are key types in the Reactor project, which is the foundation for reactive programming in Spring WebFlux. These types represent reactive data streams and are used to handle asynchronous operations.

Mono:
Represents a stream that can contain zero or one value. It is used to represent an asynchronous operation that returns a single value or completes without returning a value (e.g. Mono).

Mono's main methods:

just(value: T):
Creates a Mono that returns the specified value.

empty():
Creates an empty Mono.

error(throwable: Throwable):
Creates a Mono that will fail.

fromCallable(callable: Callable):
Creates a Mono from a function that returns a value.

map(transform: Function):
Converts a value to Mono using a function.

flatMap(transform: Function):
Converts the value to a Mono returned by the function and subscribes to it.

then():
Ignores the result and returns Mono

import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Mono


@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
  
   @GetMapping("/{id}")
    fun getUserById(@PathVariable id: String): Mono<User> {
        return userService.getUserById(id)
    }
    
    @PostMapping
    fun createUser(@RequestBody user: User): Mono<User> {
        return userService.createUser(user)
    }
    
    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable id: String): Mono<Void> {
        return userService.deleteUser(id)
    }
    
}

Flux:
Represents a stream that can contain zero or more values. It is used to represent an asynchronous data stream that can generate multiple values, such as collections or continuous data streams.

Main Flux methods:

just(values: T…):
Creates a Flux that returns the specified values.

empty():
Creates an empty Flux.

error(throwable: Throwable):
Creates a Flux that will fail.

fromIterable(iterable: Iterable):
Creates a Flux from a collection.

range(start: Int, count: Int):
Creates a Flux that generates a sequence of numbers.

map(transform: Function):
Converts each value to a Flux using a function.

flatMap(transform: Function):
Converts each value into a Flux returned by the function and subscribes to it.

filter(predicate: Predicate):
Filters values in a Flux using a predicate.

import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux


@RestController
@RequestMapping("/users")
class UserController(private val userService: UserService) {
  
    @GetMapping
    fun getAllUsers(): Flux<User> {
        return userService.getAllUsers()
    }
    
    @GetMapping("/role/{role}")
    fun getUsersByRole(@PathVariable role: String): Flux<User> {
        return userService.getUsersByRole(role)
    }
    
    @GetMapping("/usernames")
    fun getUsernames(): Flux<String> {
        return userService.getUsernames()
    }
    
}

Mono vs Flux comparison:

Mono:
Used when zero or one value is expected.
Example: Getting a user by ID (one result or none).

Flux:
Used when zero or more values are expected.
Example: Get a list of all users (can be zero, one or many users).

Example using map, flatMap and filter operations:

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono
import java.time.Duration


@RestController
class HomeController {

  
    @GetMapping("/")
    fun index(): Mono<String> {
        return Mono.just("Welcome to the home page!")
            .map { it.toUpperCase() }  // Convert the string to uppercase
            .flatMap { appendDate(it) }  // Add the current date
            .filter { it.length > 20 }  // Filter lines longer than 20 characters
            .switchIfEmpty(Mono.just("The resulting string is too short!"))  // If the string is shorter than 20 characters, return an alternative message
    }

    private fun appendDate(str: String): Mono<String> {
        return Mono.just("$str - ${java.time.LocalDate.now()}")
    }
    
}

Example using doOnNext and delayElement:

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono
import java.time.Duration


@RestController
class HomeController {
  
    @GetMapping("/")
    fun index(): Mono<String> {
        return Mono.just("Welcome to the home page!")
            .doOnNext { println("Original String: $it") }  // Logging the original line
            .map { it.reversed() }  // Reverse the line
            .delayElement(Duration.ofSeconds(2))  // 2 second delay
            .doOnNext { println("Reversed String: $it") }  // Logging the reversed string
    }
    
}

Example using zipWith to combine with another Mono:

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono


@RestController
class HomeController {
  
    @GetMapping("/")
    fun index(): Mono<String> {
        val additionalInfo = Mono.just("This is additional info")
        return Mono.just("Welcome to the home page!")
            .zipWith(additionalInfo) { original, additional ->
                "$original - $additional"
            }
    }
    
}

Example using flatMapMany to convert to Flux and then merge:

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono


@RestController
class HomeController {
  
    @GetMapping("/")
    fun index(): Mono<String> {
        return Mono.just("Welcome to the home page!")
            .flatMapMany { Flux.fromArray(it.split(" ").toTypedArray()) }  // Convert a string to a Flux of words
            .map { it.reversed() }  // We turn over each word
            .collectList()  // Let's put it back together in a list
            .map { it.joinToString(" ") }  // We combine the words back into a string
    }
    
}

Servlets are server components written in Java that are used to process requests and create dynamic content for web applications. They run on the server and are part of Java EE (now Jakarta EE) technology. Servlets allow you to create web applications that can interact with clients via HTTP.

Basic concepts of servlets:

Servlet container:
The execution environment in which servlets are executed. The container manages the servlet's life cycle, its initialization, request handling, and destruction.

HttpServlet:
The class from which all servlets inherit to handle HTTP requests.

Servlet API:
A set of classes and interfaces that provide functionality for creating servlets.

Servlet life cycle:

Initialization (init):
The init method is called by the container when the servlet is created. This method initializes the servlet and is executed once during the servlet's life cycle.

Request processing (service):
The service method is called each time the servlet receives a request from a client. This method delegates requests to the doGet, doPost, doPut, doDelete, etc. methods, depending on the type of HTTP request.

Destroy:
The destroy method is called by the container before the servlet is destroyed. This method is executed once and is used to free up resources.

Example of a simple servlet:

import javax.servlet.ServletException
import javax.servlet.annotation.WebServlet
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.io.IOException


@WebServlet("/hello")
class HelloServlet : HttpServlet() {

  
    @Throws(ServletException::class, IOException::class)
    override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
        response.contentType = "text/html"
        response.writer.use { out ->
            out.println("<html><body>")
            out.println("<h1>Hello, World!</h1>")
            out.println("</body></html>")
        }
    }

}

Servlet configuration in web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.example.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

Deploying the application:
Place your servlet and web.xml configuration file in the appropriate directories of your web application (e.g. WEB-INF/classes and WEB-INF respectively). Deploy your application to an application server such as Tomcat, WildFly, or GlassFish.

Handling different types of HTTP requests:
Servlets can handle different types of HTTP requests such as GET, POST, PUT, DELETE, etc. To do this, you need to override the corresponding methods in the servlet class.

import javax.servlet.ServletException
import javax.servlet.annotation.WebServlet
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.io.IOException


@WebServlet("/example")
class ExampleServlet : HttpServlet() {
  
    @Throws(ServletException::class, IOException::class)
    override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
    }
    
    @Throws(ServletException::class, IOException::class)
    override fun doPost(request: HttpServletRequest, response: HttpServletResponse) {
    }
    
    @Throws(ServletException::class, IOException::class)
    override fun doPut(request: HttpServletRequest, response: HttpServletResponse) {
    }
    
    @Throws(ServletException::class, IOException::class)
    override fun doDelete(request: HttpServletRequest, response: HttpServletResponse) {
    }
    
}

Using query parameters and sessions:
Servlets can extract request parameters and work with sessions to persist data between requests.
Extracting query parameters

import javax.servlet.ServletException
import javax.servlet.annotation.WebServlet
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.io.IOException


@WebServlet("/param")
class ParamServlet : HttpServlet() {
  
    @Throws(ServletException::class, IOException::class)
    override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
        val param = request.getParameter("paramName")
        response.contentType = "text/html"
        response.writer.use { out ->
            out.println("<html><body>")
            out.println("<h1>Parameter: $param</h1>")
            out.println("</body></html>")
        }
    }
    
}

Working with sessions:

import javax.servlet.ServletException
import javax.servlet.annotation.WebServlet
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.http.HttpSession
import java.io.IOException


@WebServlet("/session")
class SessionServlet : HttpServlet() {
  
    @Throws(ServletException::class, IOException::class)
    override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
        val session: HttpSession = request.session
        session.setAttribute("username", "JohnDoe")
        response.contentType = "text/html"
        response.writer.use { out ->
            out.println("<html><body>")
            out.println("<h1>Session created for user: ${session.getAttribute("username")}</h1>")
            out.println("</body></html>")
        }
    }
    
}

Public Key Infrastructure (PKI) is a system that enables the management, creation, distribution, use, storage, and revocation of digital certificates and public keys. PKI is the foundation for secure data exchange on the Internet, ensuring the authenticity, integrity, and confidentiality of information.

The main components of PKI are:

Certificate Authority (CA):
is a trusted third party that issues and manages digital certificates. A CA verifies the identity of the entity to which the certificate is issued.

Registration Authority (RA):
is responsible for verifying the identity of the subject before issuing a CA certificate. The RA acts as an intermediary between the user and the CA.

Digital Certificate:
an electronic document that links a public key to the owner's (subject's) identifier. A certificate contains information about the owner, the public key, the validity period, and the CA's digital signature.

Public and Private Keys:
The public key is used to encrypt data and verify digital signatures. It is distributed publicly.
The private key is used to decrypt data and create digital signatures. It is kept secret.

Certificate Store:
a database that stores digital certificates, private keys, and other PKI elements.

Certificate Revocation List (CRL):
a list of certificates that have been revoked before their expiration date. The CA regularly updates and distributes the CRL.

Protocols and standards:
PKI uses various standards and protocols such as X.509 for digital certificates, SSL/TLS for secure connections, and others.

How PKI works:

Request a certificate:
The user (or device) generates a key pair (public and private keys).
The user sends a Certificate Signing Request (CSR) to the CA via the RA. The CSR contains information about the user and the public key.

Verification and issuance of certificate:
RA verifies the user's identity.
The CA creates a digital certificate, signs it with its private key, and issues it to the user.

Using the certificate:
The user uses their digital certificate and associated private key to securely exchange data and digitally sign.

Certificate Authenticity Verification:
The recipient of the data uses the CA's public key to verify the digital signature of the user's certificate, confirming its authenticity.

Certificate revocation:
If the private key is compromised or the user's status changes, the certificate can be revoked. The CA adds the revoked certificate to the CRL.

Example of using PKI:
Let's look at a simple example of using PKI to secure a website with an SSL/TLS certificate.

Requesting and issuing an SSL certificate:
The website owner generates a key pair (public and private keys).
The website owner submits a CSR to the CA, providing the domain information and public key.
The CA verifies the ownership of the domain and issues an SSL certificate, signing it with its private key.

Website setup:
The website owner installs an SSL certificate and its associated private key on the web server.
The web server is configured to use SSL/TLS to establish secure connections.

Secure connection:
When a client (such as a browser) connects to a website, the server sends its SSL certificate to the client.
The client verifies the authenticity of the certificate using the CA's public key.
If the certificate is valid, a secure connection is established and the data is encrypted using the server's public key.

CSRF (Cross-Site Request Forgery) is a type of web application attack in which an attacker tricks a user into performing an unwanted action on a site where they are authenticated. The attack is carried out by sending fake requests on behalf of the user, using their authentication data (e.g. session cookies).

How CSRF works:
The user is authenticated on the website and has an active session.
The attacker creates a fake form or link on their site that sends requests to the target site on behalf of the user.
A user visiting an attacker's site accidentally performs an action on the target site (for example, transferring money or changing a password).

How to Prevent CSRF in Spring Security:
Spring Security provides built-in protection against CSRF attacks, which is enabled by default. The protection works by using CSRF tokens, which must be included in every state-changing request (e.g. POST, PUT, DELETE).

How CSRF protection works in Spring Security:
When a page with a form is loaded, Spring Security generates a unique CSRF token and adds it to a hidden field in the form.
When you submit a form, the token is sent along with the request.
The server checks the presence and correctness of the token. If the token is missing or incorrect, the request is rejected.

Example of CSRF protection setup in Spring Security:
Enable CSRF protection (usually enabled by default)
CSRF protection is enabled by default in Spring Security. To ensure this, you can explicitly specify the configuration in the SecurityConfig class.

import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter


@Configuration
@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    override fun configure(http: HttpSecurity) {
        http.authorizeRequests()
                .antMatchers("/public/**", "/login", "/register").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/home", true)
                .permitAll()
            .and()
            .csrf() // Enable CSRF protection (enabled by default)
    }

}

Adding CSRF token to forms:
Spring Security automatically adds a CSRF token to the form if Thymeleaf is used. For other template engines or manual processing, you need to explicitly include the token in the form.
Example of a form using Thymeleaf

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required />
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required />
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

In this example, the line adds the CSRF token to the form.

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />

Handling AJAX requests with CSRF:
For AJAX requests, you must include the CSRF token in the request header.
Example of AJAX request with CSRF token

function sendPostRequest() {
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $.ajax({
        url: '/your-endpoint',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({ key: 'value' }),
        beforeSend: function(xhr) {
            xhr.setRequestHeader(header, token);
        },
        success: function(response) {
            console.log('Success:', response);
        },
        error: function(error) {
            console.log('Error:', error);
        }
    });
}

Adding meta tags for CSRF token to HTML:

<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>

The Prometheus format is a standard format for collecting, storing, and presenting metrics that is used by the Prometheus monitoring system. This format includes different metric types, labels for additional information, and is used for easy data analysis and aggregation.

The main elements of the Prometheus format are:

Types of metrics:
Prometheus supports several types of metrics:

Counter:
A monotonically increasing value that is used to count something (such as the number of requests).

Gauge:
A value that can increase and decrease (for example, current memory usage).

Histogram:
Aggregates values into fixed intervals (buckets) and is used to measure the distribution of values (e.g. response times of queries).

Summary:
Similar to a histogram, but also stores quantile scores (e.g. 95th percentile of query response times).

Tags:
Tags are used to add additional information to metrics. They help filter and aggregate metrics across different dimensions.

Data format:
Metrics in Prometheus format are represented as text strings with a specific structure.

# HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="get",handler="/api"} 1027
http_requests_total{method="post",handler="/api"} 230
# HELP cpu_usage The current CPU usage.
# TYPE cpu_usage gauge
cpu_usage{core="0"} 0.75
cpu_usage{core="1"} 0.60
# HELP request_duration_seconds A histogram of the request duration.
# TYPE request_duration_seconds histogram
request_duration_seconds_bucket{le="0.1"} 24054
request_duration_seconds_bucket{le="0.2"} 33444
request_duration_seconds_bucket{le="0.5"} 100392
request_duration_seconds_bucket{le="1.0"} 129389
request_duration_seconds_bucket{le="+Inf"} 144320
request_duration_seconds_sum 53423
request_duration_seconds_count 144320
# HELP response_size_bytes The size of HTTP responses.
# TYPE response_size_bytes summary
response_size_bytes{quantile="0.5"} 512
response_size_bytes{quantile="0.9"} 1024
response_size_bytes{quantile="0.99"} 2048
response_size_bytes_sum 10554321
response_size_bytes_count 24530

Comments:
Comments start with # and provide additional information about the metrics, such as a description (HELP) and the metric type (TYPE).

Metrics:
Each metric row consists of the metric name, labels (if any), and value.

Types of metrics:

http_requests_total:
A counter that tracks the number of HTTP requests.

cpu_usage:
A gage that monitors current CPU usage.

request_duration_seconds:
A histogram tracking query duration.

response_size_bytes:
A summary tracking HTTP response sizes.

How to use Prometheus format:

dependencies {
    implementation 'io.micrometer:micrometer-registry-prometheus'
}
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.prometheus.PrometheusMeterRegistry
import io.micrometer.prometheus.PrometheusConfig


@SpringBootApplication
class Application


fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)
}


@Bean
fun prometheusMeterRegistry(): PrometheusMeterRegistry {
    return PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
}


@Bean
fun metricsCommonTags(): MeterRegistryCustomizer<MeterRegistry> {
    return MeterRegistryCustomizer { registry -> registry.config().commonTags("application", "my-app") }
}

Method for exposing metrics:

import io.micrometer.prometheus.PrometheusMeterRegistry
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController


@RestController
class MetricsController(
    private val prometheusMeterRegistry: PrometheusMeterRegistry
) {

  
    @GetMapping("/actuator/prometheus")
    fun scrape(): String {
        return prometheusMeterRegistry.scrape()
    }
    
    
}

Structured concurrency is a programming concept aimed at simplifying work with parallelism and concurrency. The basic idea is that all parallel tasks should be structured in such a way that their life cycles and task hierarchies are clearly defined and manageable. This allows for easier task management, improved code readability and reliability, and easier debugging and error handling.

Basic principles of structured competition:

Task Hierarchy:
Parallel tasks are organized into a clear hierarchy, where each task has a parent and child tasks.
The life cycle of a child task is controlled by the parent task.

Automatic task lifecycle management:
A parent task is responsible for creating and completing all of its child tasks. Completing a parent task automatically causes all of its child tasks to complete.

Explicit control of task scope:
The scope of parallel tasks is limited to code blocks, which simplifies the management of tasks and their resources.

The Happens-Before principle is a key concept in multithreading and concurrency theory that describes the ordering of operations and events in a multithreaded environment. This principle is used to determine the correctness of program execution and to ensure synchronization between threads.

The main ideas of the Happens-Before principle:

Sequence of execution in one thread:
If one operation precedes another in the same thread, the first operation will necessarily occur before the second. This ensures the program logic and order of operations within a single thread.

Synchronization using locks:
If operation A unlocks a monitor (lock) and operation B acquires the same monitor, then operation A occurs before operation B. This ensures that changes made by a thread before unlocking are visible to another thread after acquiring the lock.

Synchronization using events (e.g. Thread.join()):
If operation A calls Thread.join() on thread B, then all operations in thread B until it completes occur before Thread.join() returns in thread A.

Synchronization using volatile variables:
Writing to a volatile variable occurs before any subsequent reading of that variable by other threads. This ensures that the changes are visible.

Transitivity:
If operation A occurs before operation B, and operation B occurs before operation C, then operation A occurs before operation C.

WAR (Web Application Archive) and JAR (Java Archive) are two different archive formats used to package and distribute applications on the Java platform.

JAR files:
are used to package Java programs and libraries

WAR files:
are designed for packaging and deploying web applications on an application server.

The life cycle of beans in the Spring Framework includes several stages, from creation to destruction. Understanding the bean life cycle is important for proper resource management and configuration of beans in Spring-based applications.

The main stages of the bean life cycle in Spring are:

Creating an instance:
Creating a bean instance begins with calling the bean class constructor. This process is typically performed by the Spring container using reflection.

Dependency Injection:
Once an instance is created, the Spring container injects dependencies (via setters, constructors, or fields annotated with @Autowired).

Initialization setup (Setters and @PostConstruct):
After dependency injection, Spring calls the setup and initialization methods.
Methods annotated with @PostConstruct are called after dependencies are injected but before the bean is used.

Initialization:
If the bean implements the InitializingBean interface, Spring calls the afterPropertiesSet() method.
You can also specify the initialization method using the @Bean(initMethod=”init”) annotation in the configuration class.

Using the bean:
After all initialization methods have been executed, the bean is ready to be used in the application.

Deinitialization (DisposableBean and @PreDestroy):
When the Spring container closes (for example, when the application terminates), the beans are destroyed.
If the bean implements the DisposableBean interface, Spring calls the destroy() method.
Methods annotated with @PreDestroy are also called before the bean is destroyed.
You can also specify the deinitialization method using the @Bean(destroyMethod=”cleanup”) annotation in the configuration class.

Example of bean life cycle:

import javax.annotation.PostConstruct
import javax.annotation.PreDestroy
import org.springframework.beans.factory.DisposableBean
import org.springframework.beans.factory.InitializingBean


class MyBean : InitializingBean, DisposableBean {

init {
println("1. Constructor: Creating a bean instance")
}

@PostConstruct
fun postConstruct() {
println("3. @PostConstruct: The postConstruct() method")
}

override fun afterPropertiesSet() {
println("4. InitializingBean: The afterPropertiesSet() method")
}

fun customInit() {
println("5. @Bean(initMethod): The customInit() method")
}

@PreDestroy
fun preDestroy() {
println("7. @PreDestroy: The preDestroy() method")
}

override fun destroy() {
println("6. DisposableBean: destroy() method" 
} 

fun customDestroy() { 
println("8. @Bean(destroyMethod): Method customDestroy()") 
}
    
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration


@Configuration
class AppConfig {
  
    @Bean(initMethod = "customInit", destroyMethod = "customDestroy")
    fun myBean(): MyBean {
        return MyBean()
    }
    
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext


fun main() {
    val context = AnnotationConfigApplicationContext(AppConfig::class.java)
    context.getBean(MyBean::class.java)
    context.close()
}

Output when executing the program:

1. Constructor: Create a bean instance
2. Inject dependencies (if any)
3. @PostConstruct: The postConstruct() method
4. InitializingBean: The afterPropertiesSet() method
5. @Bean(initMethod): The customInit() method
6. Use the bean
7. @PreDestroy: The preDestroy() method
8. DisposableBean: The destroy() method
9. @Bean(destroyMethod): The customDestroy() method

Constructor:
Spring creates an instance of a bean by calling its constructor.

Dependency Injection:
Spring injects all dependencies into the bean (if any).

@PostConstruct:
The method annotated with @PostConstruct is called.

InitializingBean:
If the bean implements the InitializingBean interface, the afterPropertiesSet() method is called.

@Bean(initMethod):
If an initialization method is specified in the configuration, it is called.
Using the bin: The bin is ready for use.

@PreDestroy:
Before a bean is destroyed, a method annotated with @PreDestroy is called.

DisposableBean:
If the bean implements the DisposableBean interface, the destroy() method is called.

@Bean(destroyMethod):
If a deinitialization method is specified in the configuration, it is called.

Git Registry:
is a term commonly used to describe a system for managing versions and artifacts of software built on top of Git. This can include hosting and managing packages such as Docker images, libraries for various programming languages, and other artifacts.

GitHub Packages:
is GitHub's built-in package hosting and management service. It allows developers to publish, manage, and use packages in their projects using the same GitHub account and repositories.

GitHub Actions:
is a GitHub-integrated platform for automating CI/CD processes. It allows you to build, test, and deploy code directly from a GitHub repository using workflows written in a YAML file.

Changelog:
is a document that contains a list of changes made to a project with each version. A changelog helps track changes, bug fixes, new features, and other important events in a project's history.

Release Notes:
is a document that provides information about a specific version of software, including new features, enhancements, bug fixes, and known issues. Release Notes are often published along with the release of a new version and help users understand what has changed.

SOAP (Simple Object Access Protocol):
is a protocol for exchanging structured information messages in distributed computing environments. SOAP is a standard for web services and allows applications to interact over a network regardless of their platform and the technologies used to implement them.

A SOAP message consists of the following main parts:
Envelope: The main element that defines the beginning and end of a message. It includes two subelements:
Header (optional): Contains metainformation about the message, such as security, transaction, and routing information.
Body: Contains the main content of the message, including method calls and responses.
Fault (optional): An element used to convey error information.

Example of a SOAP message:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://www.example.org/webservice">
   <soapenv:Header/>
   <soapenv:Body>
      <web:GetUserDetails>
         <web:UserId>12345</web:UserId>
      </web:GetUserDetails>
   </soapenv:Body>
</soapenv:Envelope>

HTTP (Hypertext Transfer Protocol) and HTTPS (HTTP Secure):
The main protocols used to transfer data between clients and servers in web applications. Spring MVC and Spring WebFlux use HTTP/HTTPS to handle web requests and responses.

REST (Representational State Transfer):
an architectural style that uses the HTTP protocol to create web services that communicate using standard HTTP methods (GET, POST, PUT, DELETE, etc.). In Spring Boot, RESTful web services are implemented using annotations such as @RestController, @GetMapping, @PostMapping, etc.

WebSocket:
a protocol for two-way communication between a client and a server over a single, long-term connection. WebSockets are often used for real-time applications such as chat rooms and online games.

JMS (Java Message Service):
a standard API for sending messages and asynchronous communication between distributed components. Spring provides integration with JMS through Spring JMS.

RSocket:
a protocol for asynchronous data transfer and interaction between services. Spring supports RSocket through Spring Messaging and Spring Boot.

AMQP (Advanced Message Queuing Protocol):
protocol for exchanging messages between system components. Spring supports AMQP through the Spring AMQP project, which includes integration with RabbitMQ.

Protobuf (Protocol Buffers) is a data serialization mechanism developed by Google. It is used to efficiently exchange data between systems and store data. Protobuf allows you to define data structures and automatically generate code for serializing and deserializing these structures in various programming languages.

Basic steps of working with Protobuf:
Defining the data schema.
Compiling the diagram into code for the selected programming language.
Using the generated code to serialize and deserialize data.

Example of using Protobuf in Spring Boot:

Defining the data schema:
Create a Protobuf schema file, for example user.proto:

syntax = "proto3";
package com.example.demo;
message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}
plugins {
    id("com.google.protobuf") version "0.8.17"
    id("org.springframework.boot") version "2.5.4"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.21"
    kotlin("plugin.spring") version "1.5.21"
}
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.google.protobuf:protobuf-java:3.17.3")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.17.3"
    }
    generatedFilesBaseDir = "$projectDir/src/generated"
}
sourceSets {
    main {
        java {
            srcDirs("src/generated/main/java")
        }
    }
}
import com.example.demo.UserProtos.User
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream


@RestController
@RequestMapping("/api")
class UserController {
  
    @GetMapping("/user")
    fun getUser(): ByteArray {
        val user = User.newBuilder()
            .setId(1)
            .setName("John Doe")
            .setEmail("[email protected]")
            .build()
        val outputStream = ByteArrayOutputStream()
        user.writeTo(outputStream)
        return outputStream.toByteArray()
    }
    
    @GetMapping("/user-from-bytes")
    fun getUserFromBytes(): User {
        val userBytes = getUser()
        val inputStream = ByteArrayInputStream(userBytes)
        return User.parseFrom(inputStream)
    }
    
}
# Get serialized user
curl -X GET http://localhost:8080/api/user --output user.protobuf
# Get user from bytes
curl -X GET http://localhost:8080/api/user-from-bytes

Javadoc is a tool for automatically generating documentation for Java code. Javadoc takes comments written in a special format and generates HTML documentation from them. It is one of the standards for documenting code in Java and is widely used in professional development to create understandable and maintainable documentation.

The main elements of Javadoc are:
Javadoc uses special comments that start with /** and end with */. Within these comments, tags are used to describe different parts of the code.

Basic Javadoc tags:

@param:
Description of the method parameter.

@return:
Description of the return value of the method.

@throws or @exception:
Description of an exception that may be thrown by the method.

@see:
A link to another piece of code or documentation.

@since:
The version in which this item was added.

@deprecated:
Indicates that the element is deprecated and not recommended for use.

@author:
Author of the code.

@version:
Code version.

/**
* The class represents a user model in the system.
* <p>
* This class is used to store information about the user,
* including their id, name, and email.
* </p>
*
* @author John Doe
* @version 1.0
* @since 2023-07-05
*/
public class User {

private Long id;
private String name;
private String email;

/**
* The constructor creates a new user with the given name and email.
*
* @param name The user name.
* @param email The user's email.
*/
public User(String name, String email) {
this.name = name;
this.email = email;
}

/**
* Returns the user's id.
*
* @return The user's id.
*/
public Long getId() {
return id;
}

/**
* Sets the user's id.
*
* @param id The user's id.
*/
public void setId(Long id) {
this.id = id;
}

/**
* Returns the user's name.
*
* @return The user's name.
*/
public String getName() {
return name;
}

/**
* Sets the user's name.
*
* @param name The user's name.
*/
public void setName(String name) {
this.name = name;
}

/**
* Returns the user's email.
*
* @return The user's email.
*/
public String getEmail() {
return email;
}

/**
* Sets the user's email.
*
* @param email The user's email.
*/
public void setEmail(String email) {
this.email = email;
}

}

To generate documentation from Javadoc comments, the javadoc tool, which is included in the JDK, is used.

javadoc -d doc -sourcepath src com.example.demo

d doc:
specifies the directory for output HTML files.

sourcepath src:
specifies the path to the source files.

com.example.demo:
Specifies the package for which documentation will be generated.

Javadoc integration with Gradle:

plugins {
    id 'java'
    id 'maven-publish'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.5.4'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.5.4'
    implementation 'com.h2database:h2:1.4.200'
    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.5.4'
}

tasks.withType(Javadoc) {
    options {
        encoding = 'UTF-8'
        charSet = 'UTF-8'
        links 'https://docs.oracle.com/en/java/javase/11/docs/api/'
    }
}

task javadocJar(type: Jar) {
    archiveClassifier = 'javadoc'
    from javadoc
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
            artifact javadocJar
        }
    }
    repositories {
        maven {
            url = uri("$buildDir/repo")
        }
    }
}

tasks.withType(Javadoc):
We configure the Javadoc generation parameters, including encoding and links to external documentation.

task javadocJar:
Create a task to create a JAR file with Javadoc.

publishing:
We set up publishing artifacts, including JARs with Javadoc, to a local repository.

Syntax and declarativity:
Maven uses XML (POM file) to describe the project and its configuration. This makes it declarative and structured, but sometimes quite cumbersome and hard to read.
Gradle uses Groovy or Kotlin DSL, which makes it more flexible and concise. Groovy and Kotlin syntax allows you to write configurations that are more readable and support programming.

Performance:
Gradle is generally faster due to its incremental build and caching. Gradle only runs tasks that have changed and uses caching to avoid repetitive operations.
Maven is more linear and sequential in executing tasks, which can result in longer builds than Gradle.

Flexibility and extensibility:
Gradle is more flexible because it allows you to use a programming language (Groovy or Kotlin) to describe configurations. This allows you to easily extend and customize your build processes.
Maven is less flexible due to its declarative nature. Customizations and extensions usually require writing and connecting additional plugins.

Support for multi-project builds:
Gradle natively supports multi-project builds, making it more suitable for large projects with multiple modules.
Maven also supports multi-project builds, but the setup can be more complex and less intuitive than Gradle.

Configuration files:
Maven uses one main configuration file, pom.xml.
Gradle uses build.gradle for each project or module, and may also include settings.gradle for multi-project builds.

Plugins and dependencies:
Gradle has a more modern plugin and dependency management system. Plugins can be connected via the plugins block, which makes it easier and more intuitive.
Maven uses plugins that need to be explicitly declared in pom.xml.

Script support:
Gradle allows you to write logical conditions and loops directly in the configuration file, making it more powerful for complex build scenarios.
Maven is more declarative-oriented, which makes writing complex logic less convenient.

IDE Integration:
Gradle is supported by most modern IDEs such as IntelliJ IDEA, Eclipse and Android Studio, making it more convenient for Android developers.
Maven is also supported by all popular IDEs, but Gradle is more widespread in the Android development environment.

Model Version:
POM model version

<modelVersion>4.0.0</modelVersion>

Group ID:
Unique identifier of the organization or group that creates the project

<groupId>com.example</groupId>

Artifact ID:
Unique identifier of the project itself.

<artifactId>my-app</artifactId>

Version:
Project version.

<version>1.0-SNAPSHOT</version>

Packaging:
The type of project packaging (e.g. jar, war, pom, etc.).

<packaging>jar</packaging>

Name:
Human-readable name of the project.

<name>My App</name>

Description:
Project description.

<description>My Maven Project</description>

Dependencies:
A section that describes the project's dependencies on other libraries or modules.

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Repositories:
A section that specifies the repositories from which Maven will download dependencies.

<repositories>
    <repository>
        <id>central</id>
        <url>https://repo.maven.apache.org/maven2</url>
    </repository>
</repositories>

Build:
A section describing the instructions for building the project.

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

Properties:
A section that defines properties that can be used in the POM file.

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

Plugins:
Connecting plugins, for example, to compile Java code.

plugins {
    id 'java'
}

Group and Version:
Setting the project ID and version.

group 'com.example'
version '1.0-SNAPSHOT'

Source Compatibility:
Installing the Java language version.

sourceCompatibility = 1.8

Repositories:
Specifying repositories from which dependencies will be downloaded.

repositories {
    mavenCentral()
}

Dependencies:
Defining project dependencies.

dependencies {
    testImplementation 'junit:junit:4.12'
}

Test:
Setting up tasks for testing.

test {
    useJUnitPlatform()
}

Tasks with Type:
Setting up compilation tasks.

tasks.withType(JavaCompile) {
    options.encoding = 'UTF-8'
    sourceCompatibility = '1.8'
    targetCompatibility = '1.8'
}

Jar:
Setting up the task of packaging into a JAR file.

jar {
    manifest {
        attributes(
            'Implementation-Title': 'My App',
            'Implementation-Version': version
        )
    }
}

In Java, Supplier is a functional interface from the java.util.function package that represents a function that takes no arguments and returns a result. Supplier is used for lazy initialization, generating values on demand, and implementing various design patterns such as factories.

Key features of Supplier:

Functional interface:
An interface with a single abstract method.

The get method:
An abstract method that takes no arguments and returns a value of type T.

Example of using Supplier:

Definition of Supplier:

import java.util.function.Supplier;


public class SupplierExample {
  
    public static void main(String[] args) {
        Supplier<String> stringSupplier = () -> "Hello, World!";
        System.out.println(stringSupplier.get());
    }
    
}

Using Supplier for lazy initialization:

import java.util.function.Supplier;


public class LazyInitializationExample {
  
    private Supplier<ExpensiveObject> expensiveObjectSupplier = this::createExpensiveObject;
  
    private ExpensiveObject expensiveObject;
  
    public ExpensiveObject getExpensiveObject() {
        if (expensiveObject == null) {
            expensiveObject = expensiveObjectSupplier.get();
        }
        return expensiveObject;
    }
    
    private ExpensiveObject createExpensiveObject() {
        System.out.println("Creating Expensive Object...");
        return new ExpensiveObject();
    }
    
    public static void main(String[] args) {
        LazyInitializationExample example = new LazyInitializationExample();
        System.out.println("First call:");
        example.getExpensiveObject(); // Object will be created here
        System.out.println("Second call:");
        example.getExpensiveObject(); // Object already created, no creation message
    }
    
    static class ExpensiveObject {
        // Some expensive operations
    }
    
}

Using Supplier in collections:

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;


public class CollectionExample {
  
    public static void main(String[] args) {
        Map<String, Supplier<Object>> registry = new HashMap<>();
        registry.put("key1", () -> "Value for key1");
        registry.put("key2", () -> 12345);
        registry.put("key3", () -> new Object());
        Object value1 = registry.get("key1").get();
        Object value2 = registry.get("key2").get();
        Object value3 = registry.get("key3").get();
        System.out.println(value1); // Output: Value for key1
        System.out.println(value2); // Output: 12345
        System.out.println(value3); // Output: java.lang.Object@<hashcode>
    }
    
}

Application in Spring:
In Spring, Supplier can also be useful for lazy initialization of beans or factory methods.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Supplier;


@Configuration
public class AppConfig {
  
    @Bean
    public Supplier<MyBean> myBeanSupplier() {
        return MyBean::new;
    }
    
    @Bean
    public MyService myService(Supplier<MyBean> myBeanSupplier) {
        return new MyService(myBeanSupplier.get());
    }
    
}


class MyBean {
    // Bean definition
}


class MyService {
  
    private final MyBean myBean;
  
    public MyService(MyBean myBean) {
        this.myBean = myBean;
    }
    // Service methods
}

Record in Java and data class in Kotlin serve similar purposes — creating simple classes for storing data with minimal boilerplate code. However, there are some differences between them, due to the specifics of the languages and their frameworks.

The main differences between record in Java and data class in Kotlin are:

Immutability:
Java record: Record fields are final and immutable by default.
Kotlin data class: Fields are not final by default and can be modified. However, you can declare them as val (immutable) or var (mutable).

Inheritance support:
Java record: record does not support inheritance from other classes (but can implement interfaces).
Kotlin data class: data class can inherit other classes and implement interfaces.

Generating methods:
Java record: Automatically generates a constructor, equals, hashCode, toString methods, and field access methods.
Kotlin data class: Automatically generates a constructor, equals, hashCode, toString methods, as well as copy and componentN methods (for destructuring).

Constructors:
Java record: Allows you to define additional constructors and methods, but automatically generates a compact constructor.
Kotlin data class: Supports primary and secondary constructors.

Features of use:
Java record: Used to create simple immutable data objects. Fields must be declared in the record header parameters.
Kotlin data class: Extensive capabilities, including copy methods for cloning objects with changes to individual fields.

Java JWT (JSON Web Token) library called JJWT is designed to create and validate JWT (JSON Web Token) tokens. JJWT (Java JWT) is an open source library that simplifies working with JWT in Java applications. JWT is a compact, URL-safe way to represent tokens that can be used to transfer authenticated information between two parties.

Key Features of JJWT:

Generating JWT:
Easily create JWT with different payload and signature types.

Parsing and validating JWT:
Ability to parse and validate JWT tokens to verify their authenticity and extract the payload.

Support for various signature algorithms:
Support various signature algorithms such as HMAC, RSA and EC.

dependencies {
    implementation("io.jsonwebtoken:jjwt-api:0.11.2")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2")
}
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.security.Keys
import java.security.Key
import java.util.Date


object JwtUtil {
  
    private val key: Key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
    
    fun createToken(subject: String): String {
        return Jwts.builder()
            .setSubject(subject)
            .setIssuedAt(Date())
            .setExpiration(Date(System.currentTimeMillis() + 3600000)) // 1 hour
            .signWith(key)
            .compact()
    }
    
    fun parseToken(token: String): String? {
        return Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(token)
            .body
            .subject
    }
    
}

Creating a key:

Keys.secretKeyFor(SignatureAlgorithm.HS256):
Generates a key for the HMAC-SHA256 signature algorithm.

Token creation:

Jwts.builder():
Creates a new JwtBuilder to create a JWT.

setSubject(subject):
Sets the subject of the token.

setIssuedAt(Date()):
Sets the token issue time.

setExpiration(Date(System.currentTimeMillis() + 3600000)):
Sets the token expiration time (1 hour).

signWith(key):
Signs the token using the generated key.

compact():
Completes the creation of a token and returns it as a string.

Token parsing:

Jwts.parserBuilder():
Creates a new JwtParserBuilder for parsing JWTs.

setSigningKey(key):
Sets the key to verify the token signature.

build():
Completes the creation of the parser.

parseClaimsJws(token):
Parses and verifies the JWT signature.

body.subject:
Extracts the subject from the token payload.

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController


@RestController
class JwtController {
  
    @GetMapping("/generate")
    fun generateToken(@RequestParam subject: String): String {
        return JwtUtil.createToken(subject)
    }
    
    @GetMapping("/validate")
    fun validateToken(@RequestParam token: String): String {
        return JwtUtil.parseToken(token) ?: "Invalid token"
    }
    
}

Controller methods:

generateToken:
Processes GET requests to /generate and returns the generated JWT token for the given subject.

validateToken:
Processes GET requests to /validate and verifies the authenticity of the passed JWT token, returning the subject or an error message.

Launching the application:
When the application starts, Spring Boot automatically configures Spring MVC and creates the routes defined in the JwtController. Requests to /generate will create a new JWT token, and requests to /validate will validate the passed token.

ReactiveAuthenticationManager:
This interface is used to verify the authenticity of a user based on the provided JWT token.

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.stereotype.Component
import reactor.core.publisher.Mono


@Component
class JwtReactiveAuthenticationManager(
    private val jwtUtil: JwtUtil,
    private val userDetailsService: UserDetailsService
) : ReactiveAuthenticationManager {
  
    override fun authenticate(authentication: Authentication): Mono<Authentication> {
        val authToken = authentication.credentials.toString()
        val username = jwtUtil.extractUsername(authToken)
        return if (username.isNotEmpty() && jwtUtil.validateToken(authToken)) {
            val userDetails: UserDetails = userDetailsService.loadUserByUsername(username)
            val auth = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities)
            Mono.just(auth)
        } else {
            Mono.empty()
        }
    }
    
}


@Component
class JwtUtil(private val secret: String) {
  
    fun extractUsername(token: String): String {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).body.subject
    }
    
    fun validateToken(token: String): Boolean {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token)
            return true
        } catch (e: Exception) {
            return false
        }
    }
    
}

ServerSecurityContextRepository:
This interface is used to extract the token from the HTTP request and set the security context.

import org.springframework.http.HttpHeaders
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.context.SecurityContextImpl
import org.springframework.security.web.server.context.ServerSecurityContextRepository
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono


@Component
class JwtSecurityContextRepository(
    private val authenticationManager: ReactiveAuthenticationManager
) : ServerSecurityContextRepository {
  
    override fun save(exchange: ServerWebExchange?, context: SecurityContext?): Mono<Void> {
        // Saving security context is not required
        return Mono.empty()
    }
    
    override fun load(exchange: ServerWebExchange): Mono<SecurityContext> {
        val authHeader = exchange.request.headers.getFirst(HttpHeaders.AUTHORIZATION)
        return if (authHeader != null && authHeader.startsWith("Bearer ")) {
            val authToken = authHeader.substring(7)
            val auth = UsernamePasswordAuthenticationToken(authToken, authToken)
            authenticationManager.authenticate(auth).map { SecurityContextImpl(it) }
        } else {
            Mono.empty()
        }
    }
    
}

Now that we have ReactiveAuthenticationManager and ServerSecurityContextRepository, we need to configure security in Spring Security.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain


@Configuration
@EnableWebFluxSecurity
class SecurityConfig(
    private val authenticationManager: JwtReactiveAuthenticationManager,
    private val securityContextRepository: JwtSecurityContextRepository
) {
  
    @Bean
    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        return http
            .csrf().disable()
            .authorizeExchange()
            .pathMatchers("/login", "/register").permitAll()
            .anyExchange().authenticated()
            .and()
            .authenticationManager(authenticationManager)
            .securityContextRepository(securityContextRepository)
            .build()
    }
    
}

ReactiveAuthenticationManager:
JwtReactiveAuthenticationManager implements the ReactiveAuthenticationManager interface.
It extracts the username from the JWT token and checks its validity.
If the token is valid, returns the authentication object.

ServerSecurityContextRepository:
JwtSecurityContextRepository implements the ServerSecurityContextRepository interface.
It extracts the token from the HTTP Authorization header and authenticates it using ReactiveAuthenticationManager.

Security Configuration:
The SecurityConfig configuration class configures the chain of security filters.
Open paths (such as for login and registration) and protected paths are defined.
A custom ReactiveAuthenticationManager and ServerSecurityContextRepository are installed.

Another example:

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import java.util.*
import kotlin.collections.HashMap


data class User(
    val username: String,
    val role: String
)


@Component
class JwtUtil {
  
    @Value("\${jwt.secret}")
    lateinit var secret: String
  
    @Value("\${jwt.expiration}")
    lateinit var expirationTime: String
  
    fun extractUsername(authToken: String): String {
        return getClaimsFromToken(authToken).subject
    }
    
    fun getClaimsFromToken(authToken: String): Claims {
        val key = Base64.getEncoder().encodeToString(secret.toByteArray())
        return Jwts.parserBuilder()
            .setSigningKey(key)
            .build()
            .parseClaimsJws(authToken)
            .body
    }
    
    fun validateToken(authToken: String): Boolean {
        return getClaimsFromToken(authToken).expiration.after(Date())
    }
    
    fun generateToken(user: User): String {
        val claims = HashMap<String, Any>()
        claims["role"] = listOf(user.role)
        val expirationSeconds = expirationTime.toLong()
        val creationDate = Date()
        val expirationDate = Date(creationDate.time + expirationSeconds * 1000)
        return Jwts.builder()
            .setClaims(claims)
            .setSubject(user.username)
            .setIssuedAt(creationDate)
            .setExpiration(expirationDate)
            .signWith(Keys.hmacShaKeyFor(secret.toByteArray()))
            .compact()
    }
    
}

Lombok is a library that simplifies the process of writing Java code by providing annotations to automatically generate code such as getters, setters, constructors, and more.

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.32'
    annotationProcessor 'org.projectlombok:lombok:1.18.32'
    testCompileOnly 'org.projectlombok:lombok:1.18.32'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.32'
}

@Getter:
Generates a getter for the field.

@Setter:
Generates a setter for the field.

@ToString:
Generates a toString() method that includes all fields of the class.

@EqualsAndHashCode:
Generates equals() and hashCode() methods involving all fields of the class.

@NoArgsConstructor:
Generates a constructor with no arguments.

@RequiredArgsConstructor:
Generates a constructor for fields marked as final or @NonNull.

@AllArgsConstructor:
Generates a constructor with arguments for all fields.

@Data:
Combines several annotations: @Getter, @Setter, @ToString, @EqualsAndHashCode and @RequiredArgsConstructor.

@Value:
Denotes an immutable class, equivalent to the combination of @Getter, @AllArgsConstructor, @ToString, @EqualsAndHashCode, and makes all fields private and final.

@Builder:
Provides the Builder pattern for a class.

@Slf4j:
Generates a logger for a class using org.slf4j.Logger.

@NonNull:
Checks that a field or method parameter is not null. If the value is null, throws a NullPointerException.

@Synchronized:
Synchronizes a method or block of code, similar to the synchronized keyword, but uses the object's private monitor.

@Getter(lazy=true):
Creates lazy initialization for the field.

OpenAPI, formerly known as Swagger, is a specification for creating, describing, documenting, and consuming RESTful web services. The OpenAPI Specification (OAS) enables developers to automate the processes of API documentation, client and server code generation, and API testing.

Example OpenAPI specification:

openapi: 3.0.0
info:
  title: Simple User API
  description: A simple API to manage users.
  version: 1.0.0
servers:
  - url: http://localhost:8080/api
paths:
  /users:
    get:
      summary: Get all users
      responses:
        '200':
          description: A list of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
    post:
      summary: Create a new user
      requestBody:
        description: User to add
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
      responses:
        '201':
          description: User created
  /users/{id}:
    get:
      summary: Get a user by ID
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: A single user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string

OpenAPI Tools:

Swagger UI:
Generates interactive documentation for APIs based on the OpenAPI specification.

Swagger Editor:
A web editor for creating OpenAPI specifications.

Swagger Codegen:
A tool for generating client and server code based on the OpenAPI specification.

OpenAPI Generator:
An alternative code generation tool that supports more languages and platforms.

OpenAPI integration in Spring Boot:
To integrate OpenAPI into Spring Boot, you can use Springdoc OpenAPI.

dependencies {
    implementation 'org.springdoc:springdoc-openapi-ui:1.6.14'
}

OpenAPI setup:
Springdoc OpenAPI automatically generates an OpenAPI specification based on your Spring controllers and models.

import org.springframework.web.bind.annotation.*
import java.util.concurrent.ConcurrentHashMap


data class User(
   var id: String,
   var name: String,
   var email: String
)


@RestController
@RequestMapping("/api/users")
class UserController {
  
    private val users = ConcurrentHashMap<String, User>()
    
    @GetMapping
    fun getAllUsers(): Map<String, User> {
        return users
    }
    
    @PostMapping
    fun createUser(@RequestBody user: User): User {
        users[user.id] = user
        return user
    }
    
    @GetMapping("/{id}")
    fun getUserById(@PathVariable id: String): User? {
        return users[id]
    }

    
}

Access to Swagger UI:
Run your Spring Boot application and navigate to http://localhost:8080/swagger-ui.html to see the auto-generated documentation for your API.

This example will cover setting up OpenVPN on an Ubuntu server.

Installing OpenVPN:

sudo apt update
sudo apt install openvpn easy-rsa

Create a directory to store configuration files:

make-cadir ~/openvpn-ca
cd ~/openvpn-ca

Set up variables:

nano vars

Change the lines to match your settings:

export KEY_COUNTRY="US"
export KEY_PROVINCE="CA"
export KEY_CITY="SanFrancisco"
export KEY_ORG="MyOrg"
export KEY_EMAIL="[email protected]"
export KEY_OU="MyOrgUnit"

Generate certificates and keys:

source vars
./clean-all
./build-ca
./build-key-server server
./build-dh
openvpn --genkey --secret keys/ta.key

Set up the server configuration file:

nano /etc/openvpn/server.conf

Configuration example:

port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh2048.pem
tls-auth ta.key 0
cipher AES-256-CBC
auth SHA256
keepalive 10 120
persist-key
persist-tun
status /var/log/openvpn/status.log
log /var/log/openvpn/openvpn.log
verb 3

Start the OpenVPN server:

sudo systemctl start openvpn@server
sudo systemctl enable openvpn@server

OpenVPN client setup:
Create a client configuration file that you will use to connect to the VPN.

Example of a client configuration file:

client
dev tun
proto udp
remote YOUR_SERVER_IP 1194
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-CBC
auth SHA256
key-direction 1
verb 3
<ca>
-----BEGIN CERTIFICATE-----
# Your CA certificate here
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
# Your client certificate here
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
# Your client private key here
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
# Your TLS key here
-----END OpenVPN Static key V1-----
</tls-auth>

Keycloak is an open-source identity and access management (IAM) system designed to simplify user management, authentication, and authorization in modern applications and services. Keycloak provides various features to ensure the security of applications and services, such as single sign-on (SSO), multi-factor authentication (MFA), social authentication, and others.

First, install Keycloak. You can use Docker to do this:

docker run -p 8080:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin jboss/keycloak
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.keycloak:keycloak-spring-boot-starter:15.0.2'
}
server.port=8081
spring.main.allow-bean-definition-overriding=true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=myrealm
keycloak.resource=myclient
keycloak.public-client=true
keycloak.security-constraints[0].authRoles[0]=user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/*
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy


@Configuration
@EnableWebSecurity
class SecurityConfig : KeycloakWebSecurityConfigurerAdapter() {
  
    @Autowired
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
        auth.authenticationProvider(keycloakAuthenticationProvider())
    }
    
    @Bean
    override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
        return NullAuthenticatedSessionStrategy()
    }
    
    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        super.configure(http)
        http.authorizeRequests()
            .antMatchers("/public*").permitAll()
            .anyRequest().authenticated()
    }
    
}
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController


@RestController
class TestController {
  
    @GetMapping("/public")
    fun publicEndpoint(): String {
        return "This is a public endpoint"
    }
    
    @GetMapping("/secured")
    fun securedEndpoint(): String {
        return "This is a secured endpoint"
    }
    
}

A Java library that simplifies writing integration tests that require working with external systems such as databases, message queues, and other services wrapped in Docker containers. It provides a simple API for managing the lifecycle of Docker containers, making integration tests more isolated, reproducible, and reliable.

Key features of Testcontainers:

Ease of use:
Testcontainers provides a convenient API for starting and stopping Docker containers in tests.

Support for various systems:
Support for containers for various databases (PostgreSQL, MySQL, MongoDB, etc.), queuing systems (Kafka, RabbitMQ), and other services.

Container life cycle:
Automatic management of container lifecycle, including starting them before a test and stopping them after a test.

Network capabilities:
Support for network modes and setting up network connections between containers.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    runtimeOnly("mysql:mysql-connector-java")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.testcontainers:junit-jupiter")
    testImplementation("org.testcontainers:mysql")
}
# application-test.properties
spring.datasource.url=jdbc:tc:mysql:latest:///testdb
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.jpa.hibernate.ddl-auto=update
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id


@Entity
data class User(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    val email: String,
    val username: String
)
import org.springframework.data.jpa.repository.JpaRepository


interface UserRepository : JpaRepository<User, Long>
import org.springframework.stereotype.Service


@Service
class UserService(private val userRepository: UserRepository) {
  
    fun createUser(user: User): User {
        return userRepository.save(user)
    }
    
    fun getUserById(id: Long): User? {
        return userRepository.findById(id).orElse(null)
    }
    
    fun getAllUsers(): List<User> {
        return userRepository.findAll()
    }
    
    fun updateUser(id: Long, updatedUser: User): User? {
        val user = userRepository.findById(id).orElseThrow { RuntimeException("User not found") }
        user.email = updatedUser.email
        user.username = updatedUser.username
        return userRepository.save(user)
    }
    
    fun deleteUser(id: Long) {
        userRepository.deleteById(id)
    }
    
}
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.containers.MySQLContainer


@ExtendWith(SpringExtension::class)
@SpringBootTest
@Testcontainers
class UserServiceTest {
  
    @Autowired
    private lateinit var userService: UserService
  
    companion object {
        @Container
        val mysqlContainer = MySQLContainer<Nothing>("mysql:latest").apply {
            withDatabaseName("testdb")
            withUsername("test")
            withPassword("test")
        }
    }
    
    @Test
    fun `test createUser`() {
        val user = User(email = "[email protected]", username = "testuser")
        val savedUser = userService.createUser(user)
        assertNotNull(savedUser.id)
        assertEquals("[email protected]", savedUser.email)
        assertEquals("testuser", savedUser.username)
    }
    
    @Test
    fun `test getAllUsers`() {
        val user1 = User(email = "[email protected]", username = "testuser1")
        val user2 = User(email = "[email protected]", username = "testuser2")
        userService.createUser(user1)
        userService.createUser(user2)
        val users = userService.getAllUsers()
        assertEquals(2, users.size)
    }
    
}

@Testcontainers:
An annotation indicating that this test class uses Testcontainers.

@Container:
Indicates that the field is a container that will be automatically managed by Testcontainers.

MySQLContainer:
Testcontainers class for running MySQL container.

Jetty, Netty and Tomcat are three popular servers/containers in the Java world, each with its own features, architecture and areas of application. Let's look at the main differences between them:

Jetty:

Purpose:
Jetty is an HTTP server and Servlet container designed for hosting web applications. It supports HTTP/2, WebSocket, and servlets.

Usage:
Often used in projects that require embedding a server into applications (for example, in Spring Boot).
Suitable for development and testing, as well as for hosting full-fledged web applications.

Performance:
Handles large numbers of simultaneous requests well due to asynchronous processing. However, it may not be as high-performance and low-level as Netty.

Ease of use:
Easily integrated into projects thanks to good documentation and support in popular frameworks (e.g. Spring Boot).

Key Features:
Supports Servlet 3.1, HTTP/2, WebSocket, and asynchronous IO. Easily embeddable and modular.

Netty:

Purpose:
An asynchronous networking framework designed for building high-performance, scalable network applications. Suitable for implementing low-level network protocols.

Usage:
It is widely used for developing servers and clients for high-load systems such as distributed systems, game servers, proxy servers, and databases.

Performance:
High performance thanks to asynchronous and non-blocking architecture. Optimized to minimize latency and maximize throughput.

Ease of use:
Requires more low-level programming and knowledge of network protocols. Can be more difficult to learn than Jetty and Tomcat, but provides more control and flexibility.

Key Features:
Support TCP, UDP, HTTP, WebSocket, and other protocols. Asynchronous and non-blocking IO, NIO support.

Tomcat:

Purpose:
Servlet container and HTTP server designed for hosting Java servlets and JSP (JavaServer Pages).

Usage:
Widely used for hosting web applications, especially those that follow the Java EE specification (servlets and JSPs).

Performance:
Good performance for hosting web applications, but not as optimized for low-level network operations as Netty. Supports asynchronous request processing, but the underlying architecture is more geared toward traditional web applications.

Ease of use:
Well documented and supported in most Java development and deployment tools. Easily integrated with Java EE and Spring frameworks.

Key Features:
Supports Servlet 4.0, JSP, WebSocket, and asynchronous IO. Often used in combination with Apache HTTP Server to create hybrid solutions.

Jetty:
A good choice for embedded servers and asynchronous web applications.

Netty:
Optimal for high-performance and low-end network applications.

Tomcat:
Suitable for traditional web applications and applications that comply with the Java EE specification.

An asynchronous Java-based networking framework for developing high-performance, scalable server-side applications and clients, such as network protocols, web servers, proxy servers, databases, and more. It is widely used in the industry due to its high performance and flexibility.

Here is an example of a simple Netty-based Echo server that returns to the client the same messages it received.

dependencies {
    implementation("io.netty:netty-all:4.1.72.Final")
}
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter


class EchoServerHandler : ChannelInboundHandlerAdapter() {
  
    override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
        ctx.write(msg) // Write the received message back to the sender
        ctx.flush() // Flush all previous written messages to the sender
    }
    
    override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
        cause.printStackTrace()
        ctx.close() // Close the connection when an exception is raised
    }
    
}
import io.netty.bootstrap.ServerBootstrap
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel


class EchoServer(
  private val port: Int
) {
  
    fun start() {
        val bossGroup = NioEventLoopGroup()
        val workerGroup = NioEventLoopGroup()
        try {
            val bootstrap = ServerBootstrap()
            bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel::class.java)
                .childHandler(object : ChannelInitializer<SocketChannel>() {
                    @Throws(Exception::class)
                    override fun initChannel(ch: SocketChannel) {
                        ch.pipeline().addLast(EchoServerHandler())
                    }
                })
            val channelFuture: ChannelFuture = bootstrap.bind(port).sync()
            channelFuture.channel().closeFuture().sync()
        } finally {
            bossGroup.shutdownGracefully()
            workerGroup.shutdownGracefully()
        }
    }
    
}

fun main() {
    val port = 8080
    EchoServer(port).start()
}

EchoServerHandler:
A class that extends ChannelInboundHandlerAdapter is used to handle incoming messages.
In the channelRead method, the server simply writes and sends back the received message.
The exceptionCaught method handles exceptions that may occur during message processing.

EchoServer:
A class that represents a server. It uses ServerBootstrap to configure and start the server.
NioEventLoopGroup is used to manage I/O streams.
ChannelInitializer is used to set up a new channel by adding an EchoServerHandler instance to its pipeline.
main:
In the main method, an EchoServer instance is created and started on port 8080.

Nginx (pronounced "engin-ex") is a high-performance HTTP server and reverse proxy, as well as a mail (IMAP/POP3) proxy server. It was created by Igor Sysoev and first released in October 2004. Nginx has become very popular due to its high performance, low resource consumption, and flexibility.

Nginx is often used with Spring for various tasks such as reverse proxying, load balancing, and security. In this context, Nginx acts as a frontend server that handles incoming HTTP(S) requests and forwards them to the Spring Boot application server. Here are some examples of how Nginx can be used with Spring Boot:

Example of using Nginx with Spring Boot:

Reverse proxying:
Nginx can serve as a reverse proxy to forward requests to a Spring Boot application running on a different port.
Nginx configuration:

# Nginx configuration file (usually located in /etc/nginx/nginx.conf or /etc/nginx/sites-available/default)
server { 
listen 80; 
server_name example.com; 
location/{ 
proxy_pass http://localhost:8080; # URL of your Spring Boot application 
proxy_set_header Host $host; 
proxy_set_header X-Real-IP $remote_addr; 
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
proxy_set_header X-Forwarded-Proto $scheme; 
} 
# Setting up errors 
error_page 404 /404.html; 
location = /404.html { 
root /var/www/html; 
} 
error_page 500 502 503 504 /50x.html; 
location = /50x.html { 
root /var/www/html; 
}
}

Load Balancing:
If you have multiple instances of a Spring Boot application, Nginx can distribute requests between them, providing load balancing.
Nginx configuration for load balancing:

# Nginx configuration file
upstream spring_app {
    server localhost:8080;
    server localhost:8081;
    server localhost:8082;
}
server {
    listen 80;
    server_name example.com;
    location / {
        proxy_pass http://spring_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    error_page 404 /404.html;
    location = /404.html {
        root /var/www/html;
    }
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /var/www/html;
    }
}

SSL/TLS setup:
Nginx can handle SSL/TLS certificates to provide a secure connection (HTTPS) for your Spring Boot application.
Nginx configuration with SSL:

# Nginx configuration file
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/your/cert.pem;
    ssl_certificate_key /path/to/your/cert.key;
    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    error_page 404 /404.html;
    location = /404.html {
        root /var/www/html;
    }
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /var/www/html;
    }
}

To create a simple server socket servlet from scratch, you need to implement a server that will listen on a specific port and process HTTP requests. In this example, we will create a server that can process GET and POST requests.

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.8.0'
    id 'application'
}
repositories {
    mavenCentral()
}
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"
}
application {
    mainClass = 'SimpleHttpServerKt'
}

Using Socket:

import java.io.*
import java.net.ServerSocket
import java.net.Socket


fun main() {
    val server = SimpleHttpServer()
    server.start(8080)
}


class SimpleHttpServer {
  
    fun start(port: Int) {
        val serverSocket = ServerSocket(port)
        println("Server is listening on port $port")
        while (true) {
            val socket = serverSocket.accept()
            Thread(ClientHandler(socket)).start()
        }
    }

    
    private class ClientHandler(private val socket: Socket) : Runnable {
        override fun run() {
            socket.use {
                val input = it.getInputStream()
                val reader = BufferedReader(InputStreamReader(input))
                val output = it.getOutputStream()
                val writer = PrintWriter(output, true)
                val request = reader.readLine()
                val tokens = request.split(" ")
                val method = tokens[0]
                val path = tokens[1]
                when (method) {
                    "GET" -> handleGetRequest(writer, path)
                    "POST" -> handlePostRequest(reader, writer, path)
                    else -> {
                        writer.println("HTTP/1.1 405 Method Not Allowed")
                        writer.println("Content-Length: 0")
                        writer.println()
                    }
                }
            }
        }
        
        private fun handleGetRequest(writer: PrintWriter, path: String) {
            writer.println("HTTP/1.1 200 OK")
            writer.println("Content-Type: text/html")
            writer.println()
            writer.println("<html><body>")
            writer.println("<h1>GET request received</h1>")
            writer.println("<p>Path: $path</p>")
            writer.println("</body></html>")
        }
        
        private fun handlePostRequest(reader: BufferedReader, writer: PrintWriter, path: String) {
            val payload = StringBuilder()
            var line: String?
            while (reader.readLine().also { line = it } != "") {
                payload.append(line).append("\n")
            }
            writer.println("HTTP/1.1 200 OK")
            writer.println("Content-Type: text/html")
            writer.println()
            writer.println("<html><body>")
            writer.println("<h1>POST request received</h1>")
            writer.println("<p>Path: $path</p>")
            writer.println("<p>Payload: $payload</p>")
            writer.println("</body></html>")
        }
    }
    
}

Using AsynchronousServerSocketChannel:

import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousServerSocketChannel
import java.nio.channels.AsynchronousSocketChannel
import java.nio.channels.CompletionHandler
import java.nio.charset.Charset


fun main() {
    val server = SimpleAsyncHttpServer()
    server.start(8080)
}


class SimpleAsyncHttpServer {
  
    fun start(port: Int) {
        val serverChannel = AsynchronousServerSocketChannel.open()
        serverChannel.bind(InetSocketAddress(port))
        println("Server is listening on port $port")
        serverChannel.accept(null, object : CompletionHandler<AsynchronousSocketChannel, Void?> {
            override fun completed(clientChannel: AsynchronousSocketChannel?, attachment: Void?) {
                serverChannel.accept(null, this)
                handleClient(clientChannel)
            }
            override fun failed(exc: Throwable?, attachment: Void?) {
                println("Failed to accept connection: ${exc?.message}")
            }
        })
        // Keep the main thread alive to accept connections
        Thread.currentThread().join()
    }
    
    private fun handleClient(clientChannel: AsynchronousSocketChannel?) {
        if (clientChannel != null && clientChannel.isOpen) {
            val buffer = ByteBuffer.allocate(1024)
            clientChannel.read(buffer, buffer, object : CompletionHandler<Int, ByteBuffer> {
                override fun completed(result: Int?, attachment: ByteBuffer?) {
                    if (result != null && result > 0) {
                        attachment?.flip()
                        val request = Charset.defaultCharset().decode(attachment).toString()
                        println("Received request: $request")
                        val response = buildResponse(request)
                        writeResponse(clientChannel, response)
                        attachment?.clear()
                        clientChannel.read(attachment, attachment, this)
                    } else {
                        clientChannel.close()
                    }
                }
                override fun failed(exc: Throwable?, attachment: ByteBuffer?) {
                    println("Failed to read from client: ${exc?.message}")
                    clientChannel.close()
                }
            })
        }
    }
    
    private fun buildResponse(request: String): String {
        val lines = request.split("\r\n")
        val requestLine = lines.firstOrNull()
        val method = requestLine?.split(" ")?.get(0) ?: ""
        val path = requestLine?.split(" ")?.get(1) ?: ""
        return when (method) {
            "GET" -> {
                "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>GET request received</h1><p>Path: $path</p>"
            }
            "POST" -> {
                "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>POST request received</h1><p>Path: $path</p>"
            }
            else -> {
                "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n"
            }
        }
    }
    
    private fun writeResponse(clientChannel: AsynchronousSocketChannel, response: String) {
        val buffer = ByteBuffer.wrap(response.toByteArray(Charset.defaultCharset()))
        clientChannel.write(buffer, buffer, object : CompletionHandler<Int, ByteBuffer> {
            override fun completed(result: Int?, attachment: ByteBuffer?) {
                if (attachment?.hasRemaining() == true) {
                    clientChannel.write(attachment, attachment, this)
                } else {
                    println("Response sent to client")
                    clientChannel.close()
                }
            }
            override fun failed(exc: Throwable?, attachment: ByteBuffer?) {
                println("Failed to write to client: ${exc?.message}")
                clientChannel.close()
            }
        })
    }
    
}

Copyright: Roman Kryvolapov

☕ Buy Me a Coffee ☕