System Design
Design With Sid
system design

Building an Expense Manager Application (Splitwise)

Building an Expense Manager Application (Splitwise)

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:

  1. Project Setup: How to initialize and configure your Spring Boot project with PostgreSQL.
  2. Entity Creation: How to define entities for users, groups, and expenses.
  3. Service Implementation: How to build business logic with service classes.
  4. Controller Development: How to create REST controllers for handling requests.
  5. Testing: Basic testing to ensure your application works correctly.

Project Setup

Step 1: Initialize the Project

  1. 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
  2. Download and Extract: Download the generated ZIP file and extract it to your preferred location.

Step 2: Configure PostgreSQL

  1. 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>
    
  2. Install: Make sure your PgAdmin is up and running

  3. 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 and name.

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 and name.

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, and paidBy.

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 each SettleUp to an Expense and a User.
  • 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 between UserGroup and User, and UserGroup and Group.

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 on User 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 on Group 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 on Expense 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 on SettleUp 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 on UserGroup 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);
    }
}

Thank You for reading !!