Building an Expense Manager Application (Splitwise)

Table Of Content
- Introduction
- What You'll Learn
- Project Setup
- Step 1: Initialize the Project
- Step 2: Configure PostgreSQL
- Step 3: Designing Schema
- 3.1 User.java
- 3.2 Group.java
- 3.3 Expense.java
- 3.4 SettleUp.java
- 3.5 UserGroup.java
- Step 4: Implementing Repositories
- 4.1 ExpenseRepository.java
- 4.2 UserRepository.java
- 4.3 GroupRepository.java
- 4.4 UserGroupRepository.java
- 4.5 SettleUpRepository.java
- Step 5: Controllers
- 5.1 ExpenseController.java
- 5.2 UserController
- 5.3 SettleUpController
- 5.4 GroupController
- 5.5 UserGroupController
- 6. Service Layer
- 6.1 User Service
- 6.2 GroupService
- 6.3 ExpenseService
- 6.4 SettleUpService
- 6.5 UserGroupService
- Thank You for reading !!
Introduction
Welcome to this beginner-friendly guide on building an expense manager application! In this tutorial, we’ll create a simple expense manager using Java, Spring Boot, and PostgreSQL. Follow these steps to set up your project, define your data models, implement business logic, and create RESTful APIs.
What You'll Learn
By the end of this guide, you’ll understand:
- Project Setup: How to initialize and configure your Spring Boot project with PostgreSQL.
- Entity Creation: How to define entities for users, groups, and expenses.
- Service Implementation: How to build business logic with service classes.
- Controller Development: How to create REST controllers for handling requests.
- Testing: Basic testing to ensure your application works correctly.
Project Setup
Step 1: Initialize the Project
-
Create a New Spring Boot Project: Use Spring Initializr to generate a new project with the following options:
- Project: Maven Project
- Language: Java
- Spring Boot: 3.0.0 or later
- Dependencies: Spring Web, Spring Data JPA, PostgreSQL Driver
-
Download and Extract: Download the generated ZIP file and extract it to your preferred location.
Step 2: Configure PostgreSQL
-
Add Dependencies: Open your
pom.xml
file and add these dependencies:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>
-
Install: Make sure your PgAdmin is up and running
-
Add Configurations: Add the following configurations in
src/main/resources/application.properties
spring.application.name=expense-manager
spring.datasource.url=jdbc:postgresql://localhost:5432/expenseapp
spring.datasource.username=admin
spring.datasource.password=admin
spring.jpa.database=POSTGRESQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.generate-ddl = true
Step 3: Designing Schema
1.In this step, we’ll define the core entities for our expense manager application. These entities will map to tables in our PostgreSQL database and represent users, groups, expenses, and settlement records.
3.1 User.java
Description: Defines the User entity that maps to a table in the PostgreSQL database. This class represents users in the system.
Purpose:
@Entity
: Marks the class as a JPA entity.@Id
: Specifies the primary key of the entity.@GeneratedValue
: Defines the strategy for generating primary key values.- Fields: Represent attributes of the user, such as
id
andname
.
File Content:
package com.sid121212.systemDesign.expense_manager.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Getters and Setters
}
3.2 Group.java
Description: Defines the Group entity that represents a group of users for which expenses will be managed.
Purpose:
@Entity
: Marks the class as a JPA entity.@Id
: Specifies the primary key of the entity.@GeneratedValue
: Defines the strategy for generating primary key values.- Fields: Represent attributes of the group, such as
id
andname
.
File Content:
package com.sid121212.systemDesign.expense_manager.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Group {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Getters and Setters
}
3.3 Expense.java
Description: Defines the Expense entity, representing expenses incurred by users within a group.
Purpose:
@Entity
: Marks the class as a JPA entity.@ManyToOne
: Specifies relationships with other entities (Group and User).- Fields: Include attributes of the expense, such as
amount
,reason
, andpaidBy
.
File Content:
package com.sid121212.systemDesign.expense_manager.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.math.BigDecimal;
@Entity
public class Expense {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Group group;
private BigDecimal amount;
private String reason;
@ManyToOne
private User paidBy;
// Getters and Setters
}
3.4 SettleUp.java
Description: Defines the SettleUp entity, which records the transactions between users to settle expenses within a group.
Purpose:
@Entity
: Marks the class as a JPA entity that maps to a database table.@ManyToOne
: Specifies relationships with other entities, specifically linking eachSettleUp
to anExpense
and aUser
.- Fields: Include attributes of the settlement, such as the amount settled.
File Content:
package com.sid121212.systemDesign.expense_manager.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.math.BigDecimal;
@Entity
public class SettleUp {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Expense expense;
@ManyToOne
private User payer;
private BigDecimal amount;
// Getters and Setters
}
3.5 UserGroup.java
Description: Defines the UserGroup
entity that maps to a table in the PostgreSQL database. This class represents the relationship between users and groups.
Purpose:
@Entity
: Marks the class as a JPA entity, which will be mapped to a table in the database.@Id
: Specifies the primary key of the entity.@GeneratedValue
: Defines the strategy for generating primary key values.@ManyToOne
: Specifies the many-to-one relationship betweenUserGroup
andUser
, andUserGroup
andGroup
.
File Content:
package com.sid121212.systemDesign.expense_manager.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
public class UserGroup {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private User user;
@ManyToOne
private Group group;
// Getters and Setters
}
Step 4: Implementing Repositories
In this step, we’ll define repository interfaces for accessing the data in our PostgreSQL database. These interfaces will use Spring Data JPA to simplify CRUD operations and custom queries.
4.1 ExpenseRepository.java
Description: Defines the repository for Expense
entities. This interface provides methods to perform CRUD operations and custom queries related to expenses.
Purpose:
- Extends
JpaRepository
: Inherits basic CRUD operations and query methods. - Custom Methods: Provides additional methods to retrieve expenses based on specific criteria.
File Content:
package com.sid121212.systemDesign.expense_manager.repositories;
import com.sid121212.systemDesign.expense_manager.entities.Expense;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ExpenseRepository extends JpaRepository<Expense, Long> {
List<Expense> findByGroup(Group group);
@Query("SELECT e FROM Expense e WHERE e.paidBy.id = :userId AND e.group.id = :groupId")
List<Expense> findByPaidByAndGroup(@Param("userId") Long userId, @Param("groupId") Long groupId);
List<Expense> findByGroupId(Long groupId);
}
4.2 UserRepository.java
Description: Provides data access methods for User
entities. This interface extends JpaRepository
to leverage Spring Data JPA's built-in methods for CRUD operations.
Purpose:
JpaRepository
: Provides standard CRUD operations and additional query methods.- Custom Queries: You can define custom query methods if needed.
File Content:
package com.sid121212.systemDesign.expense_manager.repositories;
import com.sid121212.systemDesign.expense_manager.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// Additional custom query methods can be defined here if needed
}
4.3 GroupRepository.java
Description: Provides data access methods for Group
entities. This interface extends JpaRepository
to handle CRUD operations and additional queries.
Purpose:
JpaRepository
: Provides default methods for CRUD operations and more.- Custom Queries: Allows definition of additional methods for specific queries.
File Content:
package com.sid121212.systemDesign.expense_manager.repositories;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GroupRepository extends JpaRepository<Group, Long> {
// Additional custom query methods can be defined here if needed
}
4.4 UserGroupRepository.java
Description: This repository interface provides data access methods for the UserGroup
entities. It extends JpaRepository
, which includes CRUD operations and custom queries tailored for user-group relationships.
Purpose:
JpaRepository
: Inherits standard methods for CRUD operations and query execution.- Custom Queries: Defines methods to query user-group relationships efficiently.
File Content:
package com.sid121212.systemDesign.expense_manager.repositories;
import com.sid121212.systemDesign.expense_manager.entities.UserGroup;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserGroupRepository extends JpaRepository<UserGroup, Long> {
List<UserGroup> findGroupsByUserId(Long userId);
List<UserGroup> findUsersByGroupId(Long groupId);
}
4.5 SettleUpRepository.java
Description: This repository interface provides data access methods for the SettleUp
entities. It extends JpaRepository
, which enables CRUD operations and custom queries specific to the SettleUp
entity.
Purpose:
JpaRepository
: Provides built-in methods for standard CRUD operations.- Custom Queries: Defines specific queries to handle use cases related to expense settlements.
File Content:
package com.sid121212.systemDesign.expense_manager.repositories;
import com.sid121212.systemDesign.expense_manager.entities.SettleUp;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface SettleUpRepository extends JpaRepository<SettleUp, Long> {
@Query("SELECT s FROM SettleUp s WHERE s.expense.group.id = :groupId")
List<SettleUp> findByGroupId(@Param("groupId") Long groupId);
@Query("SELECT s FROM SettleUp s WHERE s.payer.id = :userId AND s.expense.group.id = :groupId")
List<SettleUp> findByPayerAndGroup(@Param("userId") Long userId, @Param("groupId") Long groupId);
@Query("SELECT s FROM SettleUp s WHERE s.expense.paidBy.id = :userId AND s.expense.group.id = :groupId")
List<SettleUp> findByPaidByAndGroup(@Param("userId") Long userId, @Param("groupId") Long groupId);
}
Step 5: Controllers
The controller layer in a Spring Boot application is responsible for handling incoming HTTP requests and mapping them to the appropriate service layer for processing. In this section, we will define the controllers for handling Expense, SettleUp, and UserGroup related requests. Each controller exposes RESTful APIs to interact with the expense manager system.
Controllers ensure a clear separation of concerns by acting as intermediaries between the client (frontend) and the business logic, which is implemented in the service layer.
5.1 ExpenseController.java
Description: This class handles HTTP requests related to expenses. It exposes RESTful endpoints for adding and retrieving expenses for groups in the system.
Purpose:
@RestController
: Marks the class as a REST controller, allowing it to handle HTTP requests.@RequestMapping
: Defines the base URL for expense-related endpoints.- Service Integration: This controller interacts with the
ExpenseService
to process business logic.
Key functionalities include:
- Adding a new expense for a group.
- Retrieving all expenses for a specific group. File Content:
package com.sid121212.systemDesign.expense_manager.controllers;
import com.sid121212.systemDesign.expense_manager.entities.Expense;
import com.sid121212.systemDesign.expense_manager.services.ExpenseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/expenses")
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
@PostMapping
public ResponseEntity<Expense> addExpense(@RequestBody Expense expense) {
Expense createdExpense = expenseService.addExpense(expense);
return ResponseEntity.ok(createdExpense);
}
@GetMapping("/group/{groupId}")
public ResponseEntity<List<Expense>> getExpensesByGroup(@PathVariable Long groupId) {
List<Expense> expenses = expenseService.getExpensesByGroup(groupId);
return ResponseEntity.ok(expenses);
}
}
5.2 UserController
The UserController
manages all operations related to users in the expense management application. It provides endpoints for creating, retrieving, and deleting users from the system.
This controller interacts with the UserService
to execute the business logic associated with user management.
Key functionalities include:
- Creating a new user.
- Retrieving user information by ID.
- Deleting a user from the system.
package com.sid121212.systemDesign.expense_manager.controllers;
import com.sid121212.systemDesign.expense_manager.entities.User;
import com.sid121212.systemDesign.expense_manager.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// Create a new user
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
// Retrieve user by ID
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// Delete a user
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
5.3 SettleUpController
The SettleUpController
handles requests related to settling expenses between users in a group. It exposes endpoints for adding settlement information and for retrieving the amount owed or owed by a specific user within a group.
This controller utilizes the SettleUpService
to manage the business logic for settling expenses.
Key functionalities include:
- Adding settlement information for a specific expense.
- Fetching the amount owed and owed by a user within a group.
package com.sid121212.systemDesign.expense_manager.controllers;
import com.sid121212.systemDesign.expense_manager.entities.SettleUp;
import com.sid121212.systemDesign.expense_manager.services.SettleUpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/settleups")
public class SettleUpController {
@Autowired
private SettleUpService settleUpService;
// Add a settlement for an expense
@PostMapping
public SettleUp addSettleUp(@RequestBody SettleUp settleUp) {
return settleUpService.addSettleUp(settleUp);
}
// Get the amount owed and owed by a user in a group
@GetMapping("/group/{groupId}/user/{userId}")
public List<SettleUp> getOwesAndOwedByUser(@PathVariable Long groupId, @PathVariable Long userId) {
return settleUpService.getOwesAndOwedByUser(groupId, userId);
}
}
5.4 GroupController
The GroupController
manages the operations related to groups in the expense manager application. It provides endpoints for creating, retrieving, updating, and deleting groups.
This controller interacts with the GroupService
to handle the business logic for managing groups, which includes associating users with groups and managing group details.
Key functionalities include:
- Creating a new group
- Retrieving details of a specific group
- Listing all groups
- Deleting or updating group details
package com.sid121212.systemDesign.expense_manager.controllers;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.services.GroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/groups")
public class GroupController {
@Autowired
private GroupService groupService;
@PostMapping
public ResponseEntity<Group> createGroup(@RequestBody Group group) {
return ResponseEntity.ok(groupService.createGroup(group));
}
@GetMapping("/{groupId}")
public ResponseEntity<Group> getGroup(@PathVariable Long groupId) {
return ResponseEntity.ok(groupService.getGroup(groupId));
}
@GetMapping
public ResponseEntity<List<Group>> getAllGroups() {
return ResponseEntity.ok(groupService.getAllGroups());
}
@DeleteMapping("/{groupId}")
public ResponseEntity<Void> deleteGroup(@PathVariable Long groupId) {
groupService.deleteGroup(groupId);
return ResponseEntity.ok().build();
}
}
5.5 UserGroupController
The UserGroupController
is responsible for managing the relationship between users and groups. It provides endpoints for adding users to a group and retrieving users in a group or groups that a user belongs to.
This controller interacts with the UserGroupService
to handle the business logic of adding users to groups and fetching relevant details about users and groups.
Key functionalities include:
- Adding multiple users to a group
- Retrieving all users in a group
- Retrieving all groups for a specific user
package com.sid121212.systemDesign.expense_manager.controllers;
import com.sid121212.systemDesign.expense_manager.services.UserGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user-groups")
public class UserGroupController {
@Autowired
private UserGroupService userGroupService;
@PostMapping("/add-users-to-group/{groupId}")
public ResponseEntity<Void> addUsersToGroup(@PathVariable Long groupId, @RequestBody List<Long> userIds) {
userGroupService.addUsersToGroup(groupId, userIds);
return ResponseEntity.ok().build();
}
@GetMapping("/users-in-group/{groupId}")
public ResponseEntity<List<Long>> getUsersInGroup(@PathVariable Long groupId) {
return ResponseEntity.ok(userGroupService.getUsersInGroup(groupId));
}
@GetMapping("/groups-for-user/{userId}")
public ResponseEntity<List<Long>> getGroupsForUser(@PathVariable Long userId) {
return ResponseEntity.ok(userGroupService.getGroupsForUser(userId));
}
}
6. Service Layer
The service layer in this application contains the business logic that bridges the gap between the controllers and the repositories. Each service handles specific logic related to the entities and ensures that the operations are executed as expected.
The following services are defined:
- UserService: Handles user-related logic.
- GroupService: Handles group-related logic.
- ExpenseService: Manages expenses and ensures correct association with users and groups.
- SettleUpService: Handles logic for settling expenses between users.
- UserGroupService: Manages relationships between users and groups.
Each of these services interacts with the repositories to perform CRUD operations and implements specific logic like adding users to groups or settling up expenses.
The service layer is crucial because it maintains separation of concerns, ensuring that the controller is only responsible for handling HTTP requests, while the business logic resides in the service layer.
Let's dive into the implementation of each service.
6.1 User Service
Description: UserService
handles all business logic related to user operations.
Purpose:
- Interacts with
UserRepository
to perform CRUD operations onUser
entities. - Implements additional logic related to user management.
File Content:
package com.sid121212.systemDesign.expense_manager.services;
import com.sid121212.systemDesign.expense_manager.entities.User;
import com.sid121212.systemDesign.expense_manager.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// Other user-related business logic
}
6.2 GroupService
Description: GroupService
manages business logic related to group operations in the expense manager application.
Purpose:
- Interacts with
GroupRepository
to perform CRUD operations onGroup
entities. - Implements additional logic for managing groups, such as creating and retrieving group details.
File Content:
package com.sid121212.systemDesign.expense_manager.services;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.repositories.GroupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class GroupService {
@Autowired
private GroupRepository groupRepository;
public Group createGroup(Group group) {
return groupRepository.save(group);
}
public Optional<Group> getGroupById(Long id) {
return groupRepository.findById(id);
}
public List<Group> getAllGroups() {
return groupRepository.findAll();
}
public Group updateGroup(Group group) {
if (groupRepository.existsById(group.getId())) {
return groupRepository.save(group);
} else {
throw new RuntimeException("Group not found with ID: " + group.getId());
}
}
public void deleteGroup(Long id) {
if (groupRepository.existsById(id)) {
groupRepository.deleteById(id);
} else {
throw new RuntimeException("Group not found with ID: " + id);
}
}
// Other group-related business logic
}
6.3 ExpenseService
Description: ExpenseService
handles the business logic for managing expenses in the expense manager application. It ensures that expenses are correctly associated with users and groups and provides methods for CRUD operations.
Purpose:
- Interacts with
ExpenseRepository
to perform CRUD operations onExpense
entities. - Implements additional logic for managing expenses, such as associating expenses with groups and users.
File Content:
package com.sid121212.systemDesign.expense_manager.services;
import com.sid121212.systemDesign.expense_manager.entities.Expense;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.entities.User;
import com.sid121212.systemDesign.expense_manager.repositories.ExpenseRepository;
import com.sid121212.systemDesign.expense_manager.repositories.GroupRepository;
import com.sid121212.systemDesign.expense_manager.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@Service
public class ExpenseService {
@Autowired
private ExpenseRepository expenseRepository;
@Autowired
private GroupRepository groupRepository;
@Autowired
private UserRepository userRepository;
public Expense createExpense(Expense expense) {
validateExpense(expense);
return expenseRepository.save(expense);
}
public Optional<Expense> getExpenseById(Long id) {
return expenseRepository.findById(id);
}
public List<Expense> getExpensesByGroupId(Long groupId) {
Group group = groupRepository.findById(groupId).orElseThrow(() -> new RuntimeException("Group not found"));
return expenseRepository.findByGroup(group);
}
public Expense updateExpense(Expense expense) {
if (expenseRepository.existsById(expense.getId())) {
validateExpense(expense);
return expenseRepository.save(expense);
} else {
throw new RuntimeException("Expense not found with ID: " + expense.getId());
}
}
public void deleteExpense(Long id) {
if (expenseRepository.existsById(id)) {
expenseRepository.deleteById(id);
} else {
throw new RuntimeException("Expense not found with ID: " + id);
}
}
private void validateExpense(Expense expense) {
if (expense.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Expense amount must be positive");
}
if (!groupRepository.existsById(expense.getGroup().getId())) {
throw new RuntimeException("Group not found");
}
if (!userRepository.existsById(expense.getPaidBy().getId())) {
throw new RuntimeException("User not found");
}
}
// Other expense-related business logic
}
6.4 SettleUpService
Description: SettleUpService
manages the logic related to settling expenses between users. It ensures that settlements are correctly processed and updates the relevant records in the database.
Purpose:
- Interacts with
SettleUpRepository
to perform CRUD operations onSettleUp
entities. - Implements logic for adding settlements and calculating amounts owed and to be paid.
File Content:
package com.sid121212.systemDesign.expense_manager.services;
import com.sid121212.systemDesign.expense_manager.entities.Expense;
import com.sid121212.systemDesign.expense_manager.entities.SettleUp;
import com.sid121212.systemDesign.expense_manager.entities.User;
import com.sid121212.systemDesign.expense_manager.repositories.ExpenseRepository;
import com.sid121212.systemDesign.expense_manager.repositories.SettleUpRepository;
import com.sid121212.systemDesign.expense_manager.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class SettleUpService {
@Autowired
private SettleUpRepository settleUpRepository;
@Autowired
private ExpenseRepository expenseRepository;
@Autowired
private UserRepository userRepository;
public SettleUp addSettlement(SettleUp settleUp) {
validateSettlement(settleUp);
return settleUpRepository.save(settleUp);
}
public Optional<SettleUp> getSettlementById(Long id) {
return settleUpRepository.findById(id);
}
public List<SettleUp> getSettlementsByGroupId(Long groupId) {
return settleUpRepository.findByGroupId(groupId);
}
public Map<User, BigDecimal> getOwesAndOwedByUser(Long userId, Long groupId) {
List<SettleUp> settlements = settleUpRepository.findByPayerAndGroup(userId, groupId);
Map<User, BigDecimal> owesMap = new HashMap<>();
for (SettleUp settleUp : settlements) {
BigDecimal amount = settleUp.getAmount();
User payer = settleUp.getPayer();
if (owesMap.containsKey(payer)) {
owesMap.put(payer, owesMap.get(payer).add(amount));
} else {
owesMap.put(payer, amount);
}
}
return owesMap;
}
private void validateSettlement(SettleUp settleUp) {
if (settleUp.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Settlement amount must be positive");
}
if (!expenseRepository.existsById(settleUp.getExpense().getId())) {
throw new RuntimeException("Expense not found");
}
if (!userRepository.existsById(settleUp.getPayer().getId())) {
throw new RuntimeException("Payer not found");
}
}
// Other settlement-related business logic
}
6.5 UserGroupService
Description: UserGroupService
manages the relationships between users and groups. It handles adding users to groups and retrieving group membership details.
Purpose:
- Interacts with
UserGroupRepository
to perform CRUD operations onUserGroup
entities. - Implements logic for adding users to groups and retrieving group memberships.
File Content:
package com.sid121212.systemDesign.expense_manager.services;
import com.sid121212.systemDesign.expense_manager.entities.Group;
import com.sid121212.systemDesign.expense_manager.entities.User;
import com.sid121212.systemDesign.expense_manager.entities.UserGroup;
import com.sid121212.systemDesign.expense_manager.repositories.GroupRepository;
import com.sid121212.systemDesign.expense_manager.repositories.UserGroupRepository;
import com.sid121212.systemDesign.expense_manager.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserGroupService {
@Autowired
private UserGroupRepository userGroupRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private GroupRepository groupRepository;
public void addUsersToGroup(Long groupId, List<Long> userIds) {
Group group = groupRepository.findById(groupId)
.orElseThrow(() -> new RuntimeException("Group not found"));
for (Long userId : userIds) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
UserGroup userGroup = new UserGroup();
userGroup.setGroup(group);
userGroup.setUser(user);
userGroupRepository.save(userGroup);
}
}
public List<Group> getGroupsForUser(Long userId) {
return groupRepository.findGroupsByUserId(userId);
}
public List<User> getUsersInGroup(Long groupId) {
return userGroupRepository.findUsersByGroupId(groupId);
}
}