This I have written as part of my LLD Practice. Please let me know further imporvements that can be done.
Also current design is Strong/Average/Lean Hire
/*
Flow->
Domain ===> Reposoitory(DB) ==> Service (BuisLogic) ==> Controller (Client Code)
/* ======= Domain ======= */
SpilitType
User
Expense
Transaction
SpilitStrategy
/* ======= Repository (DB Logic) ======= */
UserRepository
GroupRepository
ExpenseRepository
TransactionRepository
/* ======= Service (Buisness Logic) ======= */
// User Group Service too for creating/updating user/group
ExpenseService
TransactionService
/* ======= Controller (Client Interaction) ======= */
SpilitwiseController (Client Interaction)
Client Code
*//*
Requirements
1. User management.
- Track user balance
2. Group management.
- Add/remove group member
- Track group expenses
3. Expense management.
- Add expense to group/individuals
- Support different split types (EQUAL, EXACT, PERCENTAGE)
4. Balance Management
- Tracks who owes whom
- Handle settelements
5. Transaction Management
- Record transaction
- Track Payment Status
*/
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/* ======== Domain ======== */
enum SpilitType{
EQUAL, EXACT, PERCENTAGE
}
class User{
String userId;
String userName;
double balance;
}
class Group{
String groupId;
String groupName;
List<String> participantIds;
}
class Expense{
String id;
Group group;
String paidBy; //userId of payer
double amount;
SpilitType spilitType;
Map<User,Double> spilitDetails;
}
class Transaction{
String id;
String fromUserId;
String toUserId;
double amount;
boolean settled;
}
/* Strategy+Factory */
interface SpilitStrategy{
Map<User,Double> spilit(Group group, User paidBy, double amount, Map<User,Double> input);
}
class EqualSpilitStrategy implements SpilitStrategy{
@Override
public Map<User,Double> spilit(Group group, User paidBy, double amount, Map<User,Double> input){
}
}
class ExactSpilitStrategy implements SpilitStrategy{
@Override
public Map<User,Double> spilit(Group group, User paidBy, double amount, Map<User,Double> input){
}
}
class PercentageSpilitStrategy implements SpilitStrategy{
@Override
public Map<User,Double> spilit(Group group, User paidBy, double amount, Map<User,Double> input){
}
}
class SpilitStrategyFactory{
static SpilitStrategy getSpilitStrategy(SpilitType spilitType){
switch (spilitType){
case EQUAL: return new EqualSpilitStrategy();
case EXACT: return new ExactSpilitStrategy();
case PERCENTAGE: return new PercentageSpilitStrategy();
default: throw new RuntimeException("Invalid Spilit Strategy");
}
}
}
/* ======== Repository ======== */
class UserRepository{
Map<String,User> userDb=new HashMap<>();
void addUser(User user){
userDb.put(user.userId, user);
}
User getUser(String userId){
return userDb.get(userId);
}
}
class GroupRepository{
Map<String,Group> groupDb=new HashMap<>();
// add and get fn for group
}
class ExpenseRepository{
Map<String,Expense> expenseDb=new HashMap<>();
void addExpense(Expense expense){
expenseDb.put(expense.id, expense);
}
Expense getExpense(String expenseId){
return expenseDb.get(expenseId);
}
}
class TransactionRepository{
Map<String,Transaction> transactionDb=new HashMap<>();
void addTransaction(Transaction transaction){
transactionDb.put(transaction.id, transaction);
}
Transaction getTransaction(String transactionId){
return transactionDb.get(transactionId);
}
}
/* ======== Service ======== */
class ExpenseService{
ExpenseRepository expenseRepository;
synchronized Expense addExpense(String id, Group group, User paidBy, double amount, SpilitType spilitType, Map<User,Double> input){
SpilitStrategy spilitStrategy=SpilitStrategyFactory.getSpilitStrategy(spilitType);
Map<User,Double> spilitDetails=spilitStrategy.spilit(group,paidBy,amount,input);
Expense expense=new Expense();
expenseRepository.addExpense(expense);
//updating balance of each user after adding expsense
for(Map.Entry<User,Double> entry:spilitDetails.entrySet()){
User user=entry.getKey();
double share=entry.getValue();
if(!user.userId.equals(paidBy.userId)){
user.balance-=share;
paidBy.balance+=share;
}
}
return expense;
}
}
class TransactionService{
TransactionRepository repository;
synchronized Transaction addTransaction(String id, User from, User to, double amount){
Transaction transaction=new Transaction();
repository.addTransaction(transaction);
from.balance+=amount;
to.balance-=amount;
return transaction;
}
}
/* ======== Controller ======== */
class SpilitwiseController{
ExpenseService expenseService;
TransactionService transactionService;
// also have create user, create group, add user fns also
void addExpense(Group group, User paidBy, double amount, SpilitType spilitType, Map<User,Double> details){
expenseService.addExpense(UUID.randomUUID().toString(),group,paidBy,amount,spilitType,details);
}
void recordTransaction(User from, User to, double amount){
transactionService.addTransaction(UUID.randomUUID().toString(),from,to,amount);
}
}
/* ======== Client Code ======== */
public class Spilitwise {
public static void main(String[] args) {
SpilitwiseController controller=new SpilitwiseController();
}
}