sperea.es
Published on

Developing a RESTful API with Spring Boot

Authors

In a REST API, calls are implemented as HTTP requests with an interface organized around resources. Each URL represents a resource. Therefore, it is essential to understand that REST APIs, like HTTP requests, are stateless. They are limited to receiving and responding to requests using JSON.

In microservices-oriented architectures, REST APIs play a crucial role, as the standard is to use a fully RESTful API interface.

In such architectures, it becomes increasingly important to rely on technologies that help accelerate development while creating lightweight applications that consume minimal resources. This is precisely where Spring Boot comes into the picture.

We can define Spring Boot as an accelerator for creating projects with Spring. The idea is to use Spring Boot to develop Spring projects in a more agile way, following a set of conventions that prevail over configuration. Above all, it saves us from the hassle of setting up complex and heavyweight configurations with dozens of files.

The Spring Framework

There are many reasons why I would recommend using Spring over other Java frameworks like Struts. The primary reason, for me, is that it is not as "intrusive" a framework. With Spring, you can achieve a modular architecture for your software. Your classes do not have to inherit from Spring classes; instead, they can be POJOs (Plain Old Java Object). A POJO is an instance of a class that does not extend or implement anything special other than the functionalities of your system. For Java programmers, this emphasizes the use of simple classes that are not tied to any specific framework. To delve deeper into this, I recommend books like "Clean Code" and "Clean Architecture."

So, why is it still considered a framework? Because it provides mechanisms to take advantage of reliable technologies that already exist and solve specific problems very well. The key to using these mechanisms is the principle of dependency inversion.

Dependency Inversion?

The foundation of Spring, and probably its greatest superpower, is to adhere to the Dependency Inversion Principle, one of the important SOLID principles.

Why is this principle so important? Because it guarantees that our system will be flexible to changes. This is because the dependencies in our source code will be based on abstractions. In a language like Java, this means that when we use instructions like "use," "import," or "include," we refer to interfaces or some form of abstract declaration. We avoid inheriting from a concrete class, as that would tightly couple our code and make it inflexible.

Of course, this principle is best applied to classes that are not "stable," meaning those that do not change frequently. But for those that change more often, we can ensure some stability if the changes affect their implementation but not their interface.

The dependency inversion principle can also help us eliminate a dependency cycle within our architecture. Dependency cycles often lead to strong coupling, which, in turn, means that when you make a change in one part of the program and it seems to work, you may discover the next day that you unintentionally broke something else.

In this dependency diagram between components, we can see a cycle. This is an undesirable situation because if a new component needs to use component 3, it also has to be compatible with component 2. In systems with many cycles like this, changing any part of your code can inadvertently "break" another part.

To tackle this issue, we must strive to eliminate these dependency cycles. One effective method for this is the inversion of dependencies. Essentially, what we'll do is create an interface for class 2-A that implements the methods needed by class 3-A. We include this interface in our component 3 and inherit it in component 2.

By the way, there are other ways to break this cycle, for example, we could move classes 2-A and 3-A to a new component so that components 2 and 3 depend on the new one. But that's a different story. We're not here to talk about architecture; that's the topic of another tutorial.

I hope that with these examples, you've understood how the Dependency Inversion Principle allows us to develop applications that are not heavily dependent on the implementation details of the framework or the database. Spring enables us to define all these aspects through interfaces, so our code doesn't care about anything beyond those interfaces.

To achieve this, we won't invoke constructors of any class.

Inversion of Control

Spring also follows the principle of Inversion of Control (IoC). This new philosophy is very useful when using development frameworks. In IoC, the framework takes control and defines the flow of actions or the lifecycle of a request.

There are various ways to implement this, but Spring utilizes the Larman's Dependency Injection pattern.

Dependency Injection has always been one of the more challenging concepts for developers, especially those who are just starting out.

In everyday object-oriented programming, we often build and relate objects using dependencies. For example, if we have class A that uses class B, we must consider two things to use it effectively:

  • It must know its type.
  • It must create an instance of class B.
  • This creates a strong coupling between the two classes, so when we want to change one, we also have to change the other. Now imagine a real program composed of dozens or hundreds of classes with strong dependencies among them; any change in one class could collapse the whole software like a house of cards. Therefore, those of us who strive to do things correctly try to split responsibilities of objects a bit more with slightly more complex but easier-to-maintain designs.

Dependency Injection can be achieved by referencing the classes of those dependencies. However, that is not good practice because the components will have a strong relationship with each other, which will eventually lead to maintenance issues in the software.

So, in Dependency Injection, interfaces are commonly used. This way, we abstract the relationship between class A and class B, regardless of their specific implementation. This provides decoupling.

Spring can inject dependencies through constructors, methods, or properties, following the pattern to guarantee Inversion of Control.

Spring Architecture

Spring is composed of several modules. Its modular architecture allows us to declare only those modules that we genuinely need. This results in more compact and lightweight applications. In the following chapter, I will briefly describe the most important parts of Spring, which is necessary before we dive into the implementation.

Core Container

This is the most essential component of Spring, and it includes the following modules:

  • Core: This module supports features we've already discussed, such as IoC and Dependency Injection (DI).
  • Bean: This module adds typical factories like BeanFactory, responsible for instantiating different beans registered in the framework. Bean Factory is an enhanced version of the Factory pattern, one of the most widely used design patterns in Java. Imagine having an abstract class from which other classes inherit (e.g., Class A and B). The Factory pattern would involve creating a new class responsible for instantiating objects A or B based on the desired choice.
  • Context: The central part of this module is called ApplicationContext, which serves as a means to access any object defined in our application.
  • SpEL (Spring Expression Language): It is a powerful expression language that allows us to query and use the object graph at runtime.

Data Access

  • JDBC: This module provides a JDBC abstraction layer that allows direct access to databases.
  • ORM: It offers integration with popular Object-Relational Mapping (ORM) APIs such as JPA and Hibernate.
  • OXM: This module provides integration for Object-XML Mapping APIs.
  • JMS (Java Messaging Service): At some point, every developer needs to intercommunicate applications. Messaging is a method of communication between software components or applications. Messages allow loosely coupled distributed communication. When a component sends a message to a destination, the receiver retrieves it from the same destination.

Transactions

This module provides a simple API for programmatic transaction management of various complex transaction APIs such as JTA.

Web

  • Web: This module provides features that allow web-oriented integration.
  • Web-MVC: It implements the Model-View-Controller (MVC) pattern of Spring for web applications.
  • Web-Socket: For client-server communication via web sockets.
  • Web-Portlet: It provides an MVC pattern to be used in portlet environments. Portlets are modular components of user interfaces managed and displayed on a web portal. Portlets produce fragments of markup code that are added to a portal page.

Others

  • AOP (Aspect-Oriented Programming): This module provides an implementation of aspect-oriented programming, which allows a better separation of concerns within applications.
  • Aspectd: It offers integration with the Aspects module, which is an AOP framework.
  • Implementations: This module provides support for class instrumentation and implementation of a class loader, mainly used in certain server applications.
  • Messaging: It supports the Simple Text Oriented Messaging Protocol (STOMP) as a sub-protocol for Web-Sockets.
  • Test: This module supports automated testing of Spring components.

The Spring Boot IOC Container

The Spring Boot IOC container is the core of Spring. It is responsible for creating, wiring, managing, configuring, and destroying objects.

As mentioned in Part 1 of this tutorial, this container uses Dependency Injection (DI) to manage components and configure them. The objects generated by the container are called Beans.

The container needs to know which objects to instantiate and assemble. We provide this information to the container using metadata. With this metadata and the POJO classes of our application, we can initialize the context of our application and start the execution.

In earlier versions, we initiated the Spring IOC container using XML files. However, nowadays, a more straightforward method is preferred: using annotations.

To initiate the configuration of the Spring IOC container, we start with a configuration class. We indicate that a class is for configuration purposes by using the @Configuration annotation.

Let's illustrate this with the simplest example: a "Hello World."

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// Our POJO class
public class HelloWorld {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

@Configuration
public class MyConfiguration {
    @Bean(name = "Hello")
    public HelloWorld helloWorld() {
        return new HelloWorld();
    }
}

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);
        HelloWorld helloWorld = app.getBean("Hello", HelloWorld.class);
        helloWorld.setMessage("Hello World");
    }
}

In the code, we've created a POJO class called HelloWorld. As mentioned earlier, this class, which contains the business logic of our application, does not inherit or implement anything from Spring.

Next, we create the MyConfiguration class, which is responsible for adding the HelloWorld bean to the context of our application. We can distinguish it by the @Configuration annotation. With the @Bean annotation, we indicate that this method will be the source of definition for the HelloWorld bean.

From there, we only need to create a Spring application, which will also contain the static main method to serve as the entry point of our application.

The main method will execute the Spring application, passing it the context we created. We use the run method, which returns an object of type ConfigurableApplicationContext. This object contains the newly created application context.

Once we have initialized the application and loaded its context, we can proceed to instantiate and initialize beans. In this case, it's our HelloWorld POJO.

As of now, this does not even resemble a real web application. However, we are learning how to add and configure our beans. Soon, we'll dive deeper into real Spring Boot applications.

Beans in Spring Any object managed by our Spring IOC container is a Bean. When defining each bean, we specify the necessary information for the container to understand how to use it.

The lifecycle of a bean is straightforward in Spring. Spring allows specifying two methods. One method runs after the bean is instantiated, and another method runs before it is destroyed. To indicate these methods, we use annotations:


@Bean(initMethod = "initializationMethod", destroyMethod = "destructionMethod")

The information we need when defining a bean is as follows:

  • The class name representing the bean.
  • The name of the bean.
  • The scope of the objects created for this bean.
  • The method to execute when creating the bean.
  • The method to execute when destroying the bean.
  • The other beans it depends on.

When defining the bean, we have two methods:

  1. As we saw in the previous article, we can declare a configuration class for the bean.
  2. We can add a special annotation to the bean. These annotations can be @Controller, @Service, @Repository, etc., and are part of Spring's pre-defined types.

For the example from the previous article (HelloWorld), we could use the second method by simply removing the MyConfiguration class and adding the @Component(name="beanName") annotation to our POJO class.

Bean Scope

The scope defines how a bean is created and used in various contexts. To specify the scope, we use the @Scope annotation. Spring provides the following possible scopes for a bean:

  • Prototype: This scope allows creating more than one instance of the bean in the IOC container.
  • Singleton: This scope allows only a single instance of the bean to exist.
  • Request: This scope is used in web applications and defines that a new instance of the bean will be created for each HTTP request.
  • Session: Similar to Request scope, but the bean instance is tied to an HTTP session.
  • Global-Session: This scope allows defining the bean in a global session of a web application. For example, to define a Singleton-scoped bean based on the code in Chapter 2:

@Configuration
public class MyConfiguration {
    @Bean(name = "Hello")
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public HelloWorld helloWorld() {
        return new HelloWorld();
    }
}

Dependency Injection (DI)

Dependency Injection is one of the key features of Spring. It allows supplying objects to a class instead of letting that class create the object. In other words, we achieve inversion of control, removing dependencies between classes in three possible ways:

Constructor-based Dependency Injection

In this case, we use the @Autowired annotation (not required if there is only one constructor).


public class MyClass {

    private SecondaryClass privateVariable;

    @Autowired
    public MyClass(SecondaryClass injectedVariable) {
        this.privateVariable = injectedVariable;
    }
}

Setter-based Dependency Injection

Here, we implement a setter method.


public class MyClass {

    private SecondaryClass privateVariable;

    @Autowired
    public void setSecondaryClass(SecondaryClass injectedVariable) {
        this.privateVariable = injectedVariable;
    }
}

Field-based Dependency Injection

In this case, we simply add the @Autowired annotation alongside the field declaration. To perform this type of dependency injection, Spring uses Java reflection.


public class MyClass {

    @Autowired
    private SecondaryClass privateVariable;

    public MyClass() {
    }
}

Beware of Circular Dependencies

Circular dependencies arise when one Bean A depends on another Bean B, which, in turn, depends on Bean A.

This can lead to errors because Spring tries to determine the correct order in which the Beans should be created. When there are circular dependencies, this cannot be resolved, leading to errors. If we use constructor-based dependency injection, we will encounter a compilation error.

In his book "Clean Architecture," Robert C. Martin talks about this anti-pattern and how to avoid it by redesigning our dependencies to follow the Acyclic Dependencies Principle (ADP). I won't delve into this topic here because it involves architectural concepts that go beyond the scope of this discussion. Just keep in mind that it is a common problem, and avoiding circular dependencies is crucial.

By applying the Inversion of Control principle and introducing interfaces between components, we can break the cycle and avoid the error.