diff --git a/README.md b/README.md index 11da620..fb76197 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,43 @@ +# **High Level Design for SplitWise** +## Functional Requirements +~ Users Can be sign up/ sign in to your system
+~ Users can add thier contacts in the system by email or phone
+~ Users can add Expense in the System with multiple contacts
+~ Users can create groups and can add group expense with the group members
+~ System has to show balance report of all users
+~ System can show balance report of particular User
+~ System can show balance report of all and particular group
+ +## Non Functional Requirements
+~The system should be highly available.Because if the service is down , no user will be able to access it.
+ +## Extended Requirements
+~System can simplyfy debts
+~System can show passbook to user.The entries should show all the transactions a user was part of
+ +## Capacity Estimation +Read will be more than writes in this system.Lets assume if each user has 20 contacts . So read vs write ratio will be 20:1.
+## Traffic estimates
+Lets assume we have 20M new users per month with 20:1 read and write then we can expect 20*20M =>400M read
+#### Queries per Second (QPS) will be 20M / (30 days * 24 hours * 3600 sec) ~= 7.71 write/sec
+#### Consedering 20:1 read to write request 20 * 7.71 ~= 154.2 read/sec
+ +## Storage Estimates
+Lets assume we are saving the expense over the year of 5 years then 20M write request every month, total number of obects we
+expect to store will be : 20M * 5years * 30 months ~= 3Billion
+if each object stored take 500 Byte . Total storage we need is ~= 3B * 500 ~= 1.5TB
+ +## BandWidth estimates
+For Write Request
+Since there are 7.7 write request every sec , so total incoming data for our service will be : 7.7 * 500Bytes =~ 3.75 Kb/sec
+For Read Request
+Since there are 154.2 read request every sec , so total outgoing data for our service will be : 154.2 * 500Bytes =~ 75.29 Kb/sec
+ +## Basic System Design
+ +![SplitWise_HLD](https://user-images.githubusercontent.com/8611287/84053046-00810000-a9cf-11ea-8a05-3c6676b1373b.png) + + # mock-machine-coding-2 Welcome to the 2nd Mock Machine Coding Round by [workat.tech](http://workat.tech). diff --git a/SplitWise_low_level_uml.png b/SplitWise_low_level_uml.png new file mode 100644 index 0000000..66b1646 Binary files /dev/null and b/SplitWise_low_level_uml.png differ diff --git a/Splitwise.uml b/Splitwise.uml new file mode 100644 index 0000000..8c72544 --- /dev/null +++ b/Splitwise.uml @@ -0,0 +1,234 @@ + + + JAVA + com.splitwise.driver + + com.splitwise.model.User + com.splitwise.splitStrategy.impl.SplitByExact + com.splitwise.splitStrategy.impl.SplitByEqual + com.splitwise.model.UserRegistry + com.splitwise.model.ExpenseTransactionInfo + com.splitwise.transactionFactory.ExpenseTransaction + com.splitwise.process.TransactionProcesser + com.splitwise.transactionFactory.BalanceTransaction + com.splitwise.transactionFactory.Transaction + com.splitwise.io.InputTxtReader + com.splitwise.driver.SplitwiseDriver + com.splitwise.model.Expense + com.splitwise.splitStrategy.impl.SplitByPercent + com.splitwise.splitStrategy.SplitStartegy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fields + Methods + Properties + + All + private + + diff --git a/input.txt b/input.txt new file mode 100644 index 0000000..30eb7da --- /dev/null +++ b/input.txt @@ -0,0 +1,8 @@ +EXPENSE u1 1000 4 u1 u2 u3 u4 EQUAL +SHOW u4 +SHOW u1 +EXPENSE u1 1250 2 u2 u3 EXACT 370 880 +SHOW +EXPENSE u4 1200 4 u1 u2 u3 u4 PERCENT 40 20 20 20 +SHOW u1 +SHOW \ No newline at end of file diff --git a/src/main/java/com/splitwise/driver/SplitwiseDriver.java b/src/main/java/com/splitwise/driver/SplitwiseDriver.java new file mode 100644 index 0000000..dec3225 --- /dev/null +++ b/src/main/java/com/splitwise/driver/SplitwiseDriver.java @@ -0,0 +1,34 @@ +package com.splitwise.driver; + + +import com.splitwise.io.InputTxtReader; +import com.splitwise.model.User; +import com.splitwise.model.UserRegistry; + +public class SplitwiseDriver{ + private static UserRegistry userRegistry; + + public static UserRegistry getUserRegistry(){ + return userRegistry; + } + public static void main(String[] args) { + User u1 = new User("u1","88888888881","u1@abc.com"); + User u2 = new User("u2","88888888882","u3@abc.com"); + User u3 = new User("u3","88888888883","u2@abc.com"); + User u4 = new User("u4","88888888884","u4@abc.com"); + User u5 = new User("u5","88888888885","u5@abc.com"); + + + userRegistry = new UserRegistry(); + userRegistry.getUserRegistry().add(u1); + userRegistry.getUserRegistry().add(u2); + userRegistry.getUserRegistry().add(u3); + userRegistry.getUserRegistry().add(u4); + userRegistry.getUserRegistry().add(u5); + + InputTxtReader reader = new InputTxtReader(); + reader.readInputTxtFile("input.txt"); + + + } + } \ No newline at end of file diff --git a/src/main/java/com/splitwise/io/InputTxtReader.java b/src/main/java/com/splitwise/io/InputTxtReader.java new file mode 100644 index 0000000..4a21be5 --- /dev/null +++ b/src/main/java/com/splitwise/io/InputTxtReader.java @@ -0,0 +1,32 @@ +package com.splitwise.io; + + +import com.splitwise.process.TransactionProcesser; + +import java.io.*; + +public class InputTxtReader { + TransactionProcesser processor; + public InputTxtReader(){ + processor = new TransactionProcesser(); + } + public void readInputTxtFile(String fileName) { + if(fileName==null && fileName.isEmpty()){ + System.err.println("FileName is empty or Null . Aborting!!!"); + return; + } + try { + File file = new File(fileName); + BufferedReader br = new BufferedReader(new FileReader(file)); + String st; + while ((st = br.readLine()) != null) { + //System.out.println(st); + processor.process(st); + } + }catch(Exception e){ + System.err.println("Exception while reading a file"+e); + } + } + +} + diff --git a/src/main/java/com/splitwise/model/Expense.java b/src/main/java/com/splitwise/model/Expense.java new file mode 100644 index 0000000..eb020de --- /dev/null +++ b/src/main/java/com/splitwise/model/Expense.java @@ -0,0 +1,12 @@ +package com.splitwise.model; + +import java.util.List; + +public class Expense { + + private User paidByUser; + private int amount; + private int noOfPeopleInExpense; + List usersInExpense; + +} diff --git a/src/main/java/com/splitwise/model/ExpenseTransactionInfo.java b/src/main/java/com/splitwise/model/ExpenseTransactionInfo.java new file mode 100644 index 0000000..5e5a16c --- /dev/null +++ b/src/main/java/com/splitwise/model/ExpenseTransactionInfo.java @@ -0,0 +1,68 @@ +package com.splitwise.model; + +import java.util.List; + +public class ExpenseTransactionInfo { + private double amount; + private int noOfPeopleInTxn; + private List usersInTxn; + private String splitType; + private User paidByUser; + private List args; + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public int getNoOfPeopleInTxn() { + return noOfPeopleInTxn; + } + + public void setNoOfPeopleInTxn(int noOfPeopleInTxn) { + this.noOfPeopleInTxn = noOfPeopleInTxn; + } + + public List getUsersInTxn() { + return usersInTxn; + } + + public void setUsersInTxn(List usersInTxn) { + this.usersInTxn = usersInTxn; + } + + public String getSplitType() { + return splitType; + } + + public void setSplitType(String splitType) { + this.splitType = splitType; + } + + public User getPaidByUser() { + return paidByUser; + } + + public void setPaidByUser(User paidByUser) { + this.paidByUser = paidByUser; + } + + public List getArgs() { + return args; + } + + public void setArgs(List args) { + this.args = args; + } + + public ExpenseTransactionInfo(double amount, int noOfPeopleInTxn, List usersInTxn, User paidByUser, List args) { + this.amount = amount; + this.noOfPeopleInTxn = noOfPeopleInTxn; + this.usersInTxn = usersInTxn; + + this.paidByUser = paidByUser; + this.args = args; + } +} diff --git a/src/main/java/com/splitwise/model/User.java b/src/main/java/com/splitwise/model/User.java new file mode 100644 index 0000000..5ecd166 --- /dev/null +++ b/src/main/java/com/splitwise/model/User.java @@ -0,0 +1,97 @@ +package com.splitwise.model; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class User { + + private static int U_ID=0; + private int userId; + private String name; + private String phoneNo; + private String email; + private int balance; + private Map expenseKeepingBook; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + User user = (User) o; + return userId == user.userId; + } + + @Override + public int hashCode() { + return Objects.hash(userId); + } + + public User(String name, String phoneNo, String email) { + this.userId = U_ID++; + this.name = name; + this.phoneNo = phoneNo; + this.email = email; + expenseKeepingBook= new HashMap<>(); + } + + public static int getuId() { + return U_ID; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPhoneNo() { + return phoneNo; + } + + public void setPhoneNo(String phoneNo) { + this.phoneNo = phoneNo; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public int getBalance() { + return balance; + } + + public void setBalance(int balance) { + this.balance = balance; + } + + public Map getExpenseKeepingBook() { + return expenseKeepingBook; + } + + public void setExpenseKeepingBook(Map expenseKeepingBook) { + this.expenseKeepingBook = expenseKeepingBook; + } + + public static void setuId(int uId) { + U_ID = uId; + } +} diff --git a/src/main/java/com/splitwise/model/UserRegistry.java b/src/main/java/com/splitwise/model/UserRegistry.java new file mode 100644 index 0000000..fe69a74 --- /dev/null +++ b/src/main/java/com/splitwise/model/UserRegistry.java @@ -0,0 +1,26 @@ +package com.splitwise.model; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class UserRegistry { + public Set getUserRegistry() { + return userRegistry; + } + + public void setUserRegistry(Set userRegistry) { + this.userRegistry = userRegistry; + } + + private Set userRegistry = new HashSet(); + + public Optional searchUserFromRegistry(String userName){ + for(User user : userRegistry){ + if (user.getName().equalsIgnoreCase(userName)){ + return Optional.of(user); + } + } + return Optional.empty(); + } +} diff --git a/src/main/java/com/splitwise/process/TransactionProcesser.java b/src/main/java/com/splitwise/process/TransactionProcesser.java new file mode 100644 index 0000000..4a545ad --- /dev/null +++ b/src/main/java/com/splitwise/process/TransactionProcesser.java @@ -0,0 +1,28 @@ +package com.splitwise.process; + +import com.splitwise.transactionFactory.BalanceTransaction; +import com.splitwise.transactionFactory.ExpenseTransaction; +import com.splitwise.transactionFactory.Transaction; + +import java.security.BasicPermission; + +public class TransactionProcesser { + Transaction expenseProcessor; + Transaction balanceProcessor; + public TransactionProcesser(){ + expenseProcessor = new ExpenseTransaction(); + balanceProcessor = new BalanceTransaction(); + } + public void process(String input){ + String []inputArr = input.trim().split("\\s+"); + String action = inputArr[0]; + switch (action){ + case "EXPENSE": + expenseProcessor.processTransaction(inputArr); + break; + case "SHOW": + balanceProcessor.processTransaction(inputArr); + break; + } + } +} diff --git a/src/main/java/com/splitwise/splitStrategy/SplitStartegy.java b/src/main/java/com/splitwise/splitStrategy/SplitStartegy.java new file mode 100644 index 0000000..0c52253 --- /dev/null +++ b/src/main/java/com/splitwise/splitStrategy/SplitStartegy.java @@ -0,0 +1,8 @@ +package com.splitwise.splitStrategy; + +import com.splitwise.model.ExpenseTransactionInfo; + +public interface SplitStartegy { + + public void executeSplit(ExpenseTransactionInfo expTxn); +} diff --git a/src/main/java/com/splitwise/splitStrategy/impl/SplitByEqual.java b/src/main/java/com/splitwise/splitStrategy/impl/SplitByEqual.java new file mode 100644 index 0000000..9182cfa --- /dev/null +++ b/src/main/java/com/splitwise/splitStrategy/impl/SplitByEqual.java @@ -0,0 +1,34 @@ +package com.splitwise.splitStrategy.impl; + +import com.splitwise.driver.SplitwiseDriver; +import com.splitwise.model.ExpenseTransactionInfo; +import com.splitwise.model.User; +import com.splitwise.splitStrategy.SplitStartegy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class SplitByEqual implements SplitStartegy { + + @Override + public void executeSplit(ExpenseTransactionInfo expTxn) { + User user = expTxn.getPaidByUser(); + for(int i=0;i users = SplitwiseDriver.getUserRegistry().getUserRegistry(); + users = users.stream().filter(user-> !user.getExpenseKeepingBook().isEmpty()).collect(Collectors.toSet()); + if(users.isEmpty()){ + System.out.println("No balances"); + return; + } + + for(User user : users){ + Map expenseMapping = user.getExpenseKeepingBook(); + expenseMapping.forEach((u,amount)->{ + if(amount < 0){ + System.out.println("User "+user.getName()+"owes User "+u.getName()+"amount :"+(-1*amount)); + } + }); + } + + }else if(inputArr.length == 2){ + User u = SplitwiseDriver.getUserRegistry().searchUserFromRegistry(inputArr[1]).get(); + if(u.getExpenseKeepingBook().isEmpty()){ + System.out.println("No balances"); + return; + } + + Map expenseMapping = u.getExpenseKeepingBook(); + expenseMapping.forEach((user,amount)->{ + if(amount < 0){ + System.out.println("User "+u.getName()+"owes User "+user.getName()+"amount :"+(-1*amount)); + }else { + System.out.println("User "+user.getName()+"owes User "+u.getName()+"amount :"+amount); + } + }); + } + + } + + @Override + public boolean validateInput(String[] inputArr) { + if(inputArr.length > 2) + return false; + if(!inputArr[0].equalsIgnoreCase("show")) + return false; + if(inputArr.length == 2){ + String userName = inputArr[1]; + Optional user = SplitwiseDriver.getUserRegistry().searchUserFromRegistry(userName); + if(!user.isPresent()) + return false; + } + return true; + } +} diff --git a/src/main/java/com/splitwise/transactionFactory/ExpenseTransaction.java b/src/main/java/com/splitwise/transactionFactory/ExpenseTransaction.java new file mode 100644 index 0000000..4f6dcf4 --- /dev/null +++ b/src/main/java/com/splitwise/transactionFactory/ExpenseTransaction.java @@ -0,0 +1,104 @@ +package com.splitwise.transactionFactory; + +import com.splitwise.driver.SplitwiseDriver; +import com.splitwise.model.ExpenseTransactionInfo; +import com.splitwise.model.User; +import com.splitwise.splitStrategy.SplitStartegy; +import com.splitwise.splitStrategy.impl.SplitByEqual; +import com.splitwise.splitStrategy.impl.SplitByExact; +import com.splitwise.splitStrategy.impl.SplitByPercent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class ExpenseTransaction implements Transaction{ + + + SplitStartegy byEqual; + SplitStartegy byExact; + SplitStartegy byPercent; + public ExpenseTransaction(){ + byEqual = new SplitByEqual(); + byPercent = new SplitByPercent(); + byExact = new SplitByExact(); + } + @Override + public void processTransaction(String[] inputArr) { + boolean isValid = validateInput(inputArr); + if(!isValid){ + return; + } + + ExpenseTransactionInfo expTxn = getExpenseTransactionInfo(inputArr); + String splitType = inputArr[4+expTxn.getNoOfPeopleInTxn()]; + switch(splitType.toUpperCase()){ + case "EXACT": + byExact.executeSplit(expTxn); + break; + case "PERCENT": + byPercent.executeSplit(expTxn); + break; + case "EQUAL": + byEqual.executeSplit(expTxn); + break; + + } + + } + private ExpenseTransactionInfo getExpenseTransactionInfo(String[] inputArr){ + double amount = Integer.parseInt(inputArr[2]); + int noOfPeopleinTxn = Integer.parseInt(inputArr[3]); + List usersInTxn = new ArrayList(); + for(int i = 4; i < noOfPeopleinTxn+4;i++){ + String userName = inputArr[i]; + Optional u = SplitwiseDriver.getUserRegistry().searchUserFromRegistry(userName); + // TODO should be part of validation + if(u.isPresent()){ + usersInTxn.add(u.get()); + }else{ + System.err.println("User in Transaction not present in user Registory: "+ Arrays.toString(inputArr)); + //return; + } + } + String splitType = inputArr[4+noOfPeopleinTxn]; + + List args = new ArrayList<>(); + for(int i = 5+noOfPeopleinTxn;i u = SplitwiseDriver.getUserRegistry().searchUserFromRegistry(inputArr[1]); + User paidByUser = u.get(); + ExpenseTransactionInfo expTxn = new ExpenseTransactionInfo(amount,noOfPeopleinTxn,usersInTxn,paidByUser,args); + return expTxn; + } + + @Override + public boolean validateInput(String[] inputArr) { + int size = inputArr.length; + try { + int amount = Integer.parseInt(inputArr[2]); + int noOfPeopleinTxn = Integer.parseInt(inputArr[3]); + int index = 3 +noOfPeopleinTxn+1; + String splitType = inputArr[index]; + if(splitType.equalsIgnoreCase("EXACT") || splitType.equalsIgnoreCase("PERCENT")){ + if(size-1- index != noOfPeopleinTxn){ + System.err.println("Not valid Transaction: "+ Arrays.toString(inputArr)); + return false; + } + else + return true; + }else if(splitType.equalsIgnoreCase("EQUAL") && index == size-1){ + return true; + }else{ + System.err.println("Not valid amount in Transaction: "+ Arrays.toString(inputArr)); + return false; + } + }catch (NumberFormatException e){ + System.err.println("Not valid integer input in Transaction: "+ Arrays.toString(inputArr)); + return false; + } + } +} diff --git a/src/main/java/com/splitwise/transactionFactory/Transaction.java b/src/main/java/com/splitwise/transactionFactory/Transaction.java new file mode 100644 index 0000000..c50b7ef --- /dev/null +++ b/src/main/java/com/splitwise/transactionFactory/Transaction.java @@ -0,0 +1,7 @@ +package com.splitwise.transactionFactory; + +public interface Transaction { + + public void processTransaction(String [] inputArr); + public boolean validateInput(String[] inputArr); +}