Before you start writing code, it's important to pick the architectural style for the app, outline the main components and how they interact. Any experienced developer will tell you a poorly designed architecture is like a house without a foundation: it might stand for a bit, but not for long.
1. Architectural overview
Choosing an architectural style
We're building the app as a monolith. Yep, no microservices hype at this stage. Why? Because our app will be relatively small, and switching to microservices doesn't make sense. Everything will live in one project, but we'll split it into logical modules.
Application components
The parts of our app can be split into a few key blocks:
- REST API: we'll expose an HTTP interface for external consumers (e.g., the frontend).
- Service layer (business logic): this is where the main app logic lives, like data processing, validation, and transaction management.
- Database: for persisting data.
- Security: we'll protect access to our data and functionality.
Here's a sample component diagram:
+-------------------+
| REST API |
|-------------------|
| Controllers |
+-------------------+
|
v
+-------------------+
| Service layer |
|-------------------|
| Business logic |
+-------------------+
|
v
+-------------------+
| Data access |
|-------------------|
| JPA Repositories |
+-------------------+
|
v
+-------------------+
| Database |
|-------------------|
2. REST API design
First, remember that a REST API is an interface that lets you interact with the app using HTTP requests. Key REST principles:
- Resource-oriented: everything in our app is treated as a resource. For example, a user is the resource
/users. - HTTP methods: different methods for different operations:
GETto retrieve data.POSTto create new data.PUTto update data.DELETEto delete data.
- HTTP status codes: the server returns a status for the operation. For example:
200 OKfor a successful response.201 Createdfor successful resource creation.404 Not Foundfor a missing resource.
Endpoint design
We plan to create a basic CRUD for users. Here are the intended endpoints:
| Method | URL | Description |
|---|---|---|
| GET | /users |
Get list of users |
| GET | /users/{id} |
Get user by ID |
| POST | /users |
Create a new user |
| PUT | /users/{id} |
Update user data |
| DELETE | /users/{id} |
Delete a user |
Here's how it looks in the controller:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public List<User> getAllUsers() {
// Returns list of all users
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
// Gets user by id
}
@PostMapping
public User createUser(@RequestBody User user) {
// Creates a new user
}
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
// Updates user data
}
@DeleteMapping("/{id}")
public ResponseEntity
deleteUser(@PathVariable Long id) {
// Deletes a user
}
}
3. Application security
Implementing authentication using Spring Security
You already know Spring Security lets us add authentication and authorization rules.
1. Adding the dependency: In pom.xml add:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. Creating the security configuration:
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/users/**").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic(); // We use basic authentication
}
}
Here we protect the /users/** endpoints and leave the rest public.
4. Working with the database
Database design
For our app we need a users table. Example structure (ER diagram):
| Field | Data Type | Description |
|---|---|---|
| id | BIGINT | Identifier |
| name | VARCHAR | User name |
| VARCHAR | Email address | |
| password | VARCHAR | Password |
Entity implementation
Create the User entity using JPA annotations:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
// Getters and setters
}
Repository for DB access
Use Spring Data JPA to work with data:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Now we can perform CRUD operations on user data via this interface.
Connection configuration
In application.properties we configure the database connection:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
Component interaction
So, how does it all tie together?
- A REST request hits the controller.
- The controller calls the appropriate method in the service layer.
- The service layer interacts with repositories and returns the result.
- The controller returns the response to the client.
Here's an example chain:
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
}
@Service
public class UserService {
private final UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));
}
}
At this point the application architecture is ready. Next we'll dive into decomposing the project tasks so we can figure out the best way to organize work on the remaining modules.
GO TO FULL VERSION