π Welcome to our Blog on Implementing the User Service in a Spring Boot Microservice Banking Application! ππΌ
Discover the core elements and insights that shape user management in our microservices-driven banking system. Whether you're a developer or a tech enthusiast, join us for practical tips and a deep dive into building a robust User Service.
So what are the functionalities that we are gonna build?
User Registration: Enroll users seamlessly into the banking system. π₯π
Reading All Users: Retrieve a complete list of registered users. ππ₯
Reading User by ID: Access detailed user information via their unique ID. ππ
Reading User by Account ID: Efficiently fetch user details using their account ID. ππ³
Update User Status: Modify user details promptly based on user requests. ππ€
Development
Now, move to your favorite IDE or to the Spring Initializer and create a Spring boot Application with the following dependencies
Now, what are we gonna use?
Keycloak admin Client (get this from maven repository)
Model Layer
Entities
The entity model is represented using the Java Persistence API (JPA) annotations, and it follows a relational database model. Let's break down the database model aspects based on the entities in the code:
User.java
package org.training.user.service.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.training.user.service.model.Status;
import javax.persistence.*;
import java.time.LocalDate;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String emailId;
private String contactNo;
private String authId;
private String identificationNumber;
@CreationTimestamp
private LocalDate creationOn;
@Enumerated(EnumType.STRING)
private Status status;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "user_profile_id", referencedColumnName = "userProfileId")
private UserProfile userProfile;
}
User Entity:
Purpose: Security
The
User
entity primarily focuses on managing information related to user authentication and security.Fields such as
emailId
,contactNo
,authId
, andidentificationNumber
are indicative of security-related attributes.It includes a
status
field, denoting the status of the user (e.g., PENDING, APPROVED, DISABLED, REJECTED), which could be crucial for access control and account management.The
userProfileId
establishes a one-to-one relationship with theUserProfile
entity, allowing for the separation of security-related data from personal information.
UserProfile.java
package org.training.user.service.model.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userProfileId;
private String firstName;
private String lastName;
private String gender;
private String address;
private String occupation;
private String martialStatus;
private String nationality;
}
UserProfile Entity:
Purpose: Personal Information Storage
The
UserProfile
entity is designed to store personal information about a user.Fields like
firstName
,lastName
,gender
,address
,occupation
,martialStatus
, andnationality
is indicative of personal details.This separation of concerns (security-related information in
User
and personal information inUserProfile
) is a common practice to enhance security and maintain a clean and organized database structure.It allows for flexibility and scalability, as personal information can be managed independently from security-related data.
DTO, Mappers, REST responses and requests
DTOs streamline data exchange, reducing over-fetching or under-fetching. Mappers facilitate seamless translation between layers, enhancing modularity. REST responses and requests ensure standardized, predictable client-server interactions, supporting statelessness. This synergy optimizes data flow, promotes security, and adheres to RESTful principles for efficient communication in applications.
CreateUser.java
package org.training.user.service.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CreateUser {
private String firstName;
private String lastName;
private String contactNumber;
private String emailId;
private String password;
}
UserDto.java
package org.training.user.service.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.training.user.service.model.Status;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDto {
private Long userId;
private String emailId;
private String password;
private String identificationNumber;
private String authId;
private Status status;
private UserProfileDto userProfileDto;
}
UserProfileDto.java
package org.training.user.service.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserProfileDto {
private String firstName;
private String lastName;
private String gender;
private String address;
private String occupation;
private String martialStatus;
private String nationality;
}
UserUpdate.java
package org.training.user.service.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserUpdate {
private String firstName;
private String lastName;
private String contactNo;
private String address;
private String gender;
private String occupation;
private String martialStatus;
private String nationality;
}
UserUpdateStatus.java
package org.training.user.service.model.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.training.user.service.model.Status;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserUpdateStatus {
private Status status;
}
Since we frequently map Entity to DTO and vice-versa we will be introducing the BaseMapper.java which will be our abstract class, and then we will be implementing the required methods in the UserMapper.java class
BaseMapper.java
package org.training.user.service.model.mapper;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public abstract class BaseMapper<E, D> {
public abstract E convertToEntity(D dto, Object... args);
public abstract D convertToDto(E entity, Object... args);
public Collection<E> convertToEntity(Collection<D> dto, Object... args) {
return dto.stream().map(d -> convertToEntity(d, args)).collect(Collectors.toList());
}
public Collection<D> convertToDto(Collection<E> entities, Object... args) {
return entities.stream().map(entity ->convertToDto(entity, args)).collect(Collectors.toList());
}
public List<E> covertToEntityList(Collection<D> dtos, Object... args) {
return convertToEntity(dtos, args).stream().collect(Collectors.toList());
}
public List<D> convertToDtoList(Collection<E> entities, Object... args) {
return convertToDto(entities, args).stream().collect(Collectors.toList());
}
public Set<E> convertToEntitySet(Collection<D> dtos, Object... args) {
return convertToEntity(dtos, args).stream().collect(Collectors.toSet());
}
public Set<D> convertToDtoSet(Collection<E> entities, Object... args) {
return convertToDto(entities, args).stream().collect(Collectors.toSet());
}
}
UserMapper.java
package org.training.user.service.model.mapper;
import org.modelmapper.ModelMapper;
import org.springframework.beans.BeanUtils;
import org.training.user.service.model.dto.UserDto;
import org.training.user.service.model.dto.UserProfileDto;
import org.training.user.service.model.entity.User;
import org.training.user.service.model.entity.UserProfile;
import java.util.Objects;
public class UserMapper extends BaseMapper<User, UserDto>{
private final ModelMapper mapper = new ModelMapper();
@Override
public User convertToEntity(UserDto dto, Object... args) {
User user = new User();
if(!Objects.isNull(dto)){
BeanUtils.copyProperties(dto, user);
if(!Objects.isNull(dto.getUserProfileDto())){
UserProfile userProfile = new UserProfile();
BeanUtils.copyProperties(dto.getUserProfileDto(), userProfile);
user.setUserProfile(userProfile);
}
}
return user;
}
@Override
public UserDto convertToDto(User entity, Object... args) {
UserDto userDto = new UserDto();
if(!Objects.isNull(entity)){
BeanUtils.copyProperties(entity, userDto);
if(!Objects.isNull(entity.getUserProfile())) {
UserProfileDto userProfileDto = new UserProfileDto();
BeanUtils.copyProperties(entity.getUserProfile(), userProfileDto);
userDto.setUserProfileDto(userProfileDto);
}
}
return userDto;
}
}
Repository Layer
The repository provides a clean and high-level interface for interacting with the database, abstracting away the complexities of data access. It promotes code reusability, consistency, and adherence to best practices in database operations within the context of a Spring Boot application.
UserRepository.java
package org.training.user.service.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.training.user.service.model.entity.User;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findUserByAuthId(String authId);
}
Service Implementation
We will be creating two services: one to communicate with the Keycloak instance and another to communicate with the database.
Keycloak Configuration
Let's create a Keycloak service that facilitates communication between the Keycloak instance and the user service.
First, we will create another client banking-service-api-client.
Then we will enble Client authentication as we did before, and also enable the Service account roles which enables you can manage the authorization and capabilities of the service accounts, ensuring they have the necessary permissions to perform specific tasks or access particular resources.
Finally, change the Valid redirect URL to *.
Next, navigate to the Service Account Roles section via the navigation bar to assign the manage-users role from the realm-management label. .
Now, click Assign Roles and apply filters by clients. Find and enable the manage-user role.
Now, all the required configurations for using the client to communicate with the user service and Keycloak instance are added.
For a detailed document containing all the steps and additional explanations, please refer to the comprehensive setup guide provided with the below document.
Now, let's add some configurations that will be helpful in establishing communication.
app:
config:
keycloak:
server-url: http://localhost:8571
realm: banking-service
client-id: banking-service-api-client
client-secret: 932OFITe8vtH4z0G9gem4Afd69JhbwrI
Now, we will create a class that will be helpful in mapping the above configuration with Java classes.
KeyCloakProperties.java
package org.training.user.service.config;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class KeyCloakProperties {
@Value("${app.config.keycloak.server-url}")
private String serverUrl;
@Value("${app.config.keycloak.realm}")
private String realm;
@Value("${app.config.keycloak.client-id}")
private String clientId;
@Value("${app.config.keycloak.client-secret}")
private String clientSecret;
private static Keycloak keycloakInstance = null;
public Keycloak getKeycloakInstance() {
if (keycloakInstance == null) {
keycloakInstance = KeycloakBuilder.builder()
.serverUrl(serverUrl)
.realm(realm)
.clientId(clientId)
.clientSecret(clientSecret)
.grantType("client_credentials")
.build();
}
return keycloakInstance;
}
public String getRealm() {
return realm;
}
}
@Slf4j
: Lombok annotation for automatic generation of a logger field.@Component
: Indicates that this class is a Spring component, and its instance can be managed by the Spring IoC container.serverUrl
,realm
,clientId
, andclientSecret
: Configuration properties for the Keycloak server connection.getKeycloakInstance()
: Method to retrieve or create a singleton instance of the Keycloak client.Checks if the
keycloakInstance
is null; if yes, creates a new instance using the provided configuration properties.The Keycloak instance is configured with the server URL, realm, client ID, client secret, and grant type.
KeyCloakManager.java
package org.training.user.service.config;
import lombok.RequiredArgsConstructor;
import org.keycloak.admin.client.resource.RealmResource;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class KeyCloakManager {
private final KeyCloakProperties keyCloakProperties;
public RealmResource getKeyCloakInstanceWithRealm() {
return keyCloakProperties.getKeycloakInstance().realm(keyCloakProperties.getRealm());
}
}
Returns a
RealmResource
instance for the specified Keycloak realm.Calls the
getKeycloakInstance()
method fromKeyCloakProperties
to obtain the Keycloak client instance.Uses the configured realm from
KeyCloakProperties
to retrieve theRealmResource
.
Keycloak Service
KeyCloakService.java
package org.training.user.service.service;
import org.keycloak.representations.idm.UserRepresentation;
import java.util.List;
public interface KeycloakService {
Integer createUser(UserRepresentation userRepresentation);
List<UserRepresentation> readUserByEmail(String emailId);
List<UserRepresentation> readUsers(List<String> authIds);
UserRepresentation readUser(String authId);
void updateUser(UserRepresentation userRepresentation);
}
KeycloakServiceImpl.java
package org.training.user.service.service.implementation;
import lombok.RequiredArgsConstructor;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.stereotype.Service;
import org.training.user.service.config.KeyCloakManager;
import org.training.user.service.service.KeycloakService;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class KeycloakServiceImpl implements KeycloakService {
private final KeyCloakManager keyCloakManager;
@Override
public Integer createUser(UserRepresentation userRepresentation) {
return keyCloakManager.getKeyCloakInstanceWithRealm().users().create(userRepresentation).getStatus();
}
@Override
public List<UserRepresentation> readUserByEmail(String emailId) {
return keyCloakManager.getKeyCloakInstanceWithRealm().users().search(emailId);
}
@Override
public List<UserRepresentation> readUsers(List<String> authIds) {
return authIds.stream().map(authId -> {
UserResource usersResource = keyCloakManager.getKeyCloakInstanceWithRealm().users().get(authId);
return usersResource.toRepresentation();
}).collect(Collectors.toList());
}
@Override
public UserRepresentation readUser(String authId) {
UsersResource userResource = keyCloakManager.getKeyCloakInstanceWithRealm().users();
return userResource.get(authId).toRepresentation();
}
@Override
public void updateUser(UserRepresentation userRepresentation) {
keyCloakManager.getKeyCloakInstanceWithRealm().users()
.get(userRepresentation.getId()).update(userRepresentation);
}
}
User Service
We will be creating different methods that help us in achieving different goals like creating user, reading users, deleting user and also updating them.
UserService.java
package org.training.user.service.service;
import org.training.user.service.model.dto.CreateUser;
import org.training.user.service.model.dto.UserDto;
import org.training.user.service.model.dto.UserUpdate;
import org.training.user.service.model.dto.UserUpdateStatus;
import org.training.user.service.model.dto.response.Response;
import java.util.List;
public interface UserService {
Response createUser(CreateUser userDto);
List<UserDto> readAllUsers();
UserDto readUser(String authId);
Response updateUserStatus(Long id, UserUpdateStatus userUpdate);
Response updateUser(Long id, UserUpdate userUpdate);
UserDto readUserById(Long userId);
UserDto readUserByAccountId(String accountId);
}
The
readUserByAccountId(String accountId)
method will be implemented in future articles as it requires inter-service communication.
UserServiceImpl.java
package org.training.user.service.service.implementation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.training.user.service.exception.EmptyFields;
import org.training.user.service.exception.ResourceConflictException;
import org.training.user.service.exception.ResourceNotFound;
import org.training.user.service.external.AccountService;
import org.training.user.service.model.Status;
import org.training.user.service.model.dto.CreateUser;
import org.training.user.service.model.dto.UserDto;
import org.training.user.service.model.dto.UserUpdate;
import org.training.user.service.model.dto.UserUpdateStatus;
import org.training.user.service.model.dto.response.Response;
import org.training.user.service.model.entity.User;
import org.training.user.service.model.entity.UserProfile;
import org.training.user.service.model.external.Account;
import org.training.user.service.model.mapper.UserMapper;
import org.training.user.service.repository.UserRepository;
import org.training.user.service.service.KeycloakService;
import org.training.user.service.service.UserService;
import org.training.user.service.utils.FieldChecker;
import javax.transaction.Transactional;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final KeycloakService keycloakService;
private final AccountService accountService;
private UserMapper userMapper = new UserMapper();
@Value("${spring.application.success}")
private String responseCodeSuccess;
@Value("${spring.application.not_found}")
private String responseCodeNotFound;
@Override
public Response createUser(CreateUser userDto) {
List<UserRepresentation> userRepresentations = keycloakService.readUserByEmail(userDto.getEmailId());
if(userRepresentations.size() > 0) {
log.error("This emailId is already registered as a user");
throw new ResourceConflictException("This emailId is already registered as a user");
}
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setUsername(userDto.getEmailId());
userRepresentation.setFirstName(userDto.getFirstName());
userRepresentation.setLastName(userDto.getLastName());
userRepresentation.setEmailVerified(false);
userRepresentation.setEnabled(false);
userRepresentation.setEmail(userDto.getEmailId());
CredentialRepresentation credentialRepresentation = new CredentialRepresentation();
credentialRepresentation.setValue(userDto.getPassword());
credentialRepresentation.setTemporary(false);
userRepresentation.setCredentials(Collections.singletonList(credentialRepresentation));
Integer userCreationResponse = keycloakService.createUser(userRepresentation);
if (userCreationResponse.equals(201)) {
List<UserRepresentation> representations = keycloakService.readUserByEmail(userDto.getEmailId());
UserProfile userProfile = UserProfile.builder()
.firstName(userDto.getFirstName())
.lastName(userDto.getLastName()).build();
User user = User.builder()
.emailId(userDto.getEmailId())
.contactNo(userDto.getContactNumber())
.status(Status.PENDING).userProfile(userProfile)
.authId(representations.get(0).getId())
.identificationNumber(UUID.randomUUID().toString()).build();
userRepository.save(user);
return Response.builder()
.responseMessage("User created successfully")
.responseCode(responseCodeSuccess).build();
}
throw new RuntimeException("User with identification number not found");
}
@Override
public List<UserDto> readAllUsers() {
List<User> users = userRepository.findAll();
Map<String, UserRepresentation> userRepresentationMap = keycloakService.readUsers(users.stream().map(user -> user.getAuthId()).collect(Collectors.toList()))
.stream().collect(Collectors.toMap(UserRepresentation::getId, Function.identity()));
return users.stream().map(user -> {
UserDto userDto = userMapper.convertToDto(user);
UserRepresentation userRepresentation = userRepresentationMap.get(user.getAuthId());
userDto.setUserId(user.getUserId());
userDto.setEmailId(userRepresentation.getEmail());
userDto.setIdentificationNumber(user.getIdentificationNumber());
return userDto;
}).collect(Collectors.toList());
}
@Override
public UserDto readUser(String authId) {
User user = userRepository.findUserByAuthId(authId).
orElseThrow(() -> new ResourceNotFound("User not found on the server"));
UserRepresentation userRepresentation = keycloakService.readUser(authId);
UserDto userDto = userMapper.convertToDto(user);
userDto.setEmailId(userRepresentation.getEmail());
return userDto;
}
@Override
public Response updateUserStatus(Long id, UserUpdateStatus userUpdate) {
User user = userRepository.findById(id).orElseThrow(
() -> new ResourceNotFound("User not found on the server"));
if (FieldChecker.hasEmptyFields(user)) {
log.error("User is not updated completely");
throw new EmptyFields("please updated the user", responseCodeNotFound);
}
if(userUpdate.getStatus().equals(Status.APPROVED)){
UserRepresentation userRepresentation = keycloakService.readUser(user.getAuthId());
userRepresentation.setEnabled(true);
userRepresentation.setEmailVerified(true);
keycloakService.updateUser(userRepresentation);
}
user.setStatus(userUpdate.getStatus());
userRepository.save(user);
return Response.builder()
.responseMessage("User updated successfully")
.responseCode(responseCodeSuccess).build();
}
@Override
public UserDto readUserById(Long userId) {
return userRepository.findById(userId)
.map(user -> userMapper.convertToDto(user))
.orElseThrow(() -> new ResourceNotFound("User not found on the server"));
}
@Override
public Response updateUser(Long id, UserUpdate userUpdate) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFound("User not found on the server"));
user.setContactNo(userUpdate.getContactNo());
BeanUtils.copyProperties(userUpdate, user.getUserProfile());
userRepository.save(user);
return Response.builder()
.responseCode(responseCodeSuccess)
.responseMessage("user updated successfully").build();
}
}
Now that all the services are done, we will focus on the Controller Layer to expose these methods as API Endpoints.
Controller Layer
package org.training.user.service.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.training.user.service.model.dto.CreateUser;
import org.training.user.service.model.dto.UserDto;
import org.training.user.service.model.dto.UserUpdate;
import org.training.user.service.model.dto.UserUpdateStatus;
import org.training.user.service.model.dto.response.Response;
import org.training.user.service.service.UserService;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/register")
public ResponseEntity<Response> createUser(@RequestBody CreateUser userDto) {
log.info("creating user with: {}", userDto.toString());
return ResponseEntity.ok(userService.createUser(userDto));
}
@GetMapping
public ResponseEntity<List<UserDto>> readAllUsers() {
return ResponseEntity.ok(userService.readAllUsers());
}
@GetMapping("auth/{authId}")
public ResponseEntity<UserDto> readUserByAuthId(@PathVariable String authId) {
log.info("reading user by authId");
return ResponseEntity.ok(userService.readUser(authId));
}
@PatchMapping("/{id}")
public ResponseEntity<Response> updateUserStatus(@PathVariable Long id, @RequestBody UserUpdateStatus userUpdate) {
log.info("updating the user with: {}", userUpdate.toString());
return new ResponseEntity<>(userService.updateUserStatus(id, userUpdate), HttpStatus.OK);
}
@PutMapping("{id}")
public ResponseEntity<Response> updateUser(@PathVariable Long id, @RequestBody UserUpdate userUpdate) {
return new ResponseEntity<>(userService.updateUser(id, userUpdate), HttpStatus.OK);
}
@GetMapping("/{userId}")
public ResponseEntity<UserDto> readUserById(@PathVariable Long userId) {
log.info("reading user by ID");
return ResponseEntity.ok(userService.readUserById(userId));
}
}
All the configuration required in application.yml
are given below:
server:
port: 8082
spring:
application:
name: user-service
bad_request: 400
conflict: 409
success: 200
not_found: 404
datasource:
url: jdbc:mysql:localhost:3306/user_service
username: root
password: root
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
app:
config:
keycloak:
server-url: http://localhost:8571
realm: banking-service
client-id: banking-service-api-client
client-secret: 932OFITe8vtH4z0G9gem4Afd69JhbwrI
Now, add the route defination(if not added) in the API Gateway that we created in Mastering Microservices: Setting API Gateway with Spring Cloud Gateway to allow the API Gateway to identify this service.
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
If any of you receive an exception when communication between the keycloak instance and service like:
java.lang.NoSuchMethodError: javax.ws.rs.core.UriBuilder.resolveTemplates(Ljava/util/Map;)Ljavax/ws/rs/core/UriBuilder;
at org.keycloak.admin.client.Keycloak.realm(Keycloak.java:114)
This issue primarily arises from the Eureka client dependency we are using. We need to exclude the javax.ws.rs.jsr311-api module, as it is the main cause of the exception we are encountering, so update the dependency as follows.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</exclusion>
</exclusions>
</dependency>
Note: Please don't try to run the application, since we need to add inter - service configuration and exception handling configuration.
Postman Collection
I'm using Postman to test the APIs, and I will be attaching the Postman collection below so that you can go through it.
Conclusion
Thanks for reading our latest article on Mastering Microservices: Implemenatation of User Service with practical usage.
You can get source code for this tutorial from our GitHub repository.
Happy Coding!!!!π