How to design Restful service request when there is multiple subclasses? - java

I have got 2 end points which are
http://localhost:8080/account/v1/credit/{accountNumber} (POST -> TransactionDTO)
http://localhost:8080/account/v1/debit/{accountNumber} (POST -> TransactionDTO)
And my TransactionDTO:
public class TransactionDTO {
#NotNull
private Double amount;
public TransactionDTO(#NotNull Double amount) {
super();
this.amount = amount;
}
... Getters Setters
}
Basically, when credit is called, the amount of dto is added to the account's balance associated with the given accountNumber, so far no problem.
However, when debit is called, as you can see below, there are multiple withdraw transaction types(and more withdraw transaction types can be added as the app gets bigger) such as BillPaymentTransaction, WithdrawTransaction.
My problem is how should I design the request class(TransactionDTO) so that when it comes to the controller I can understand which type of withdraw transaction came and do the specific action for it.
My controller's methods:
#PostMapping(path = "/credit/{accountNumber}")
public ResponseEntity<TransactionStatus> credit(#PathVariable String accountNumber, #RequestBody TransactionDTO depositTransaction) throws InsufficientBalanceException {
TransactionStatus response = accountService.credit(accountNumber, depositTransaction);
return new ResponseEntity<>(response,HttpStatus.OK);
}
#PostMapping(path = "/debit/{accountNumber}")
public ResponseEntity<TransactionStatus> debit(#PathVariable String accountNumber, #RequestBody TransactionDTO withdrawalTransaction) throws InsufficientBalanceException {
return new ResponseEntity<>(accountService.debit(accountNumber, withdrawalTransaction),HttpStatus.OK);
}

If you want/need to have just one endpoint for multiple types of withdrawals, you could have an Enum defining the different types and add that as a field to the TransactionDTO.
Something like this:
public enum DebitType { BILL_PAYMENT, WITHDRAW, ... }
public class DebitTransactionDTO {
#NotNull
private Double amount;
#NotNull
private DebitType debitType;
public DebitTransactionDTO(#NotNull Double amount, #NotNull DebitType debitType) {
super();
this.amount = amount;
this.debitType = debitType;
}
... Getters Setters
}
Since you probably do not want to have the debit type in the credit transaction, you might want to have separate DTO classes for credit and debit (hence the DebitTransactionDTO class name).

Related

How to get values from generic parameter (not related to any class) in Spring boot

I'm new with SB & trying to enhance my skill. I make a little API project, in which I've account numbers with account balance property, and goal of the project is to adding up amount in account balance property with corresponding account number.
I'm stuck at the point where I need to get value from amount variable that is actually not the part of any class. Anyone can change the title if you think that it doesn't in according with the subject.
I mention hereunder classes, you can find my efforts which is useless as it seems messed up.
I'd grateful if anyone can correct or give me the reference stack where the same question lies.
Account.java
private Long id;
private int accountNumber;
private BigDecimal accountBalance;
AccountRepository
public interface AccountDao extends CrudRepository<Account, Long> {
Account findByAccountNumber(int accountNumber);
}
AccountService
public interface AccountService {
void deposit(int accountNumber, String accountType, double amount);
} // these fields are not related to class field, I took this to send data to API
AccountServiceImpl
public void deposit(String accountType, int accountNumber, double amount) {
if (accountType.equalsIgnoreCase("Permanent")) {
Account account = accountRepository.findByAccountNumber(accountNumber);
account.setAccountBalance(account.getAccountBalance().add(new
BigDecimal(amount)));
accountDao.save(account);
AccountController
#PostMapping("/deposit")
public ResponseEntity<Object> depositPost(#RequestBody double amount, String accountType,
int accountNumber, Account account){
accountService.deposit(accountType, accountNumber,
Double.parseDouble(String.valueOf(amount)));
return ResponseEntity.ok("Record has been entered");
}
Error I receive is:
2021-10-21 20:19:58.660 WARN 22892 --- [nio-8088-exec-1]
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error:
Cannot deserialize value of type `double` from Object value (token
`JsonToken.START_OBJECT`); nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of
type `double` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (PushbackInputStream); line: 1, column: 1]]
The issue is your POST body. I am guessing you are sending similar to the following:
{
"amount": 1000
}
This is a JSON object that can't be deserialized into a simple double parameter. You need to create a corresponding class as follows:
public class AccountDto {
private double amount;
// constructor, getters, setters
}
Now you need to use it in your Controller:
#PostMapping("/deposit")
public ResponseEntity<Object> depositPost(#RequestBody AccountDto accountDto, String accountType, int accountNumber, Account account){
accountService.deposit(accountType, accountNumber, accountDto.getAmount());
return ResponseEntity.ok("Record has been entered");
}

Accessing account balance in Bank System application

I am currently trying to create a simple bank program in which users can create an account, deposit and withdraw money. The problem is that I am having trouble with creating accounts.
Here is my main:
Account account = new Account();
account.createAccount(1000, "Bob");
Here is my Account.java class:
public void createAccount(int bal, String name){
int balance = bal;
String username = name;
System.out.println("An account has been set up for "+name+", with a balance of $"+bal+".");
The problem I'm having is accessing the accounts which are created.
The account is created and the string runs, but I am not sure how I would be able to access the account.
I want to add a method in which you can call the account's balance but am not sure how to do this.
Any help would be greatly appreciated.
You aren't showing much of your Account class (which is fine -- having a Minimal Reproducible Example is good, but a fully compilable code snippet would be preferable), so my answer will go with some assumptions:
You need to be able to retrieve and set the balance.
You don't currently have a solution for this problem.
I'll assume your current class looks like this (eliminating whitespace for brevity):
public class Account {
public void createAccount(int bal, String name) {
int balance = bal;
String username = name;
System.out.println("An account has been set up for "+name+", with a balance of $"+bal+".");
}
}
As #ScaryWombat pointed out in his comment, you need to move the balance and username variables out of the createAccount method and into the class as fields. You then set these fields in the createAccount method:
public class Account {
private int balance = bal;
private String username = name;
public void createAccount(int bal, String name) {
this.balance = bal;
this.username = name;
System.out.println("An account has been set up for "+name+", with a balance of $"+bal+".");
}
}
Now, an Account instance holds a balance and a name, which is overwritten when you call createAccount.
However, we still have a problem. Every time you call createAccount, the data is overwritten. For example in this code, Bob's account will be forgotten and the data overwritten by Alice's account:
Account account = new Account();
account.createAccount(1000, "Bob");
account.createAccount(2000, "Alice");
This is where constructors come in. Instead of a createAccount method, we'll move the logic into the Account constructor:
public class Account {
private int balance = bal;
private String username = name;
public Account(int bal, String name) {
this.balance = bal;
this.username = name;
System.out.println("An account has been set up for "+name+", with a balance of $"+bal+".");
}
}
With this, the only way to create an account is to specify the starting details in the call to new Account():
Account bobsAccount = new Account(1000, "Bob");
Account alicesAccount = new Account(2000, "Alice");
However, we still haven't addressed the core of your question: How to access the balance? The following will result in an error:
bobsAccount.balance = 2000;
This is because the balance field is private (as it should be). What we need is to add accessor methods to the Account class:
public class Account{
...
public void setBalance(int newBalance) {
balance = newBalance;
System.out.println(name+"'s account balance is now $"+bal+".");
}
public int getBalance() {
return balance;
}
...
}
You might ask "why go to this trouble?", well, we can add checks (e.g. not allow a balance to go below zero), add logging to audit access to balances, or even not have a setBalance or getBalance, but only transferFrom(Account otherAccount) and transferTo(Account otherAccount) (ensuring money is never added, but only transferred around in the system).
PS. and as pointed out in other comments, int is never good for monetery amounts. What you need is a Currency type that uses something else, e.g. BigDecimal, to store the actual values.

How do I create objects using more than one constructor?

I wrote the following code and driver program but I am not sure how to create bank account objects using both constructors. One constructor takes an initial balance and the second constructor opens an account with no money. Also, should the accountBalance include validity checks?
Optionally, I could do the following:
Include a fee as part of what describes a bank account. Update the BankAccount class as necessary. The user should be able to set the fee amount for each account and add the fee through a method. Add code to the driver program to demonstrate the fee functionality. (Could someone explain to me what this is asking)
//Bank Account class
import java.text.NumberFormat;
public class BankAccount {
private String ownerName;
private String accountId;
private double accountBalance;
public BankAccount(String ownerName, String accountId, double accountBalance) {
this.ownerName = ownerName;
this.accountId = accountId;
if(accountBalance >= 0) {
this.accountBalance = accountBalance;
} else {
System.out.println("Due to your negative account balace, you will be issued a fee.\n");
}
}
public BankAccount(double accountBalance) {
accountBalance = 0;
}
public String getOwnerName() {
return ownerName;
}
public void setOwnerName(String ownerName) {
this.ownerName = ownerName;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public double getAccountBalance() {
return accountBalance;
}
public void setAccountBalance(double accountBalance) {
if(accountBalance >= 0) {
this.accountBalance = accountBalance;
} else {
System.out.println("Due to your negative account balace, you will be issued a fee.\n");
}
}
public void withdraw(double amount) {
if(amount > 0 && amount < accountBalance) {
accountBalance -= amount;
} else {
System.out.println("Invalid withdraw amount! Please try again.\n");
}
}
public void deposit(double amount) {
if(amount > 0) {
accountBalance += amount;
} else {
System.out.println("Invalid deposit amount! Please try again.\n");
}
}
public String toString() {
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
return "Account Owner's Name: " + ownerName + "\n" + "Account ID: " + accountId + "\n" +
"Balance in the account: " + currencyFormatter.format(accountBalance);
}
}
//Driver Program
public class BankAccountDriver {
public static void main(String[] args) {
BankAccount myAccount = new BankAccount("Smith", "123jahgsd", 1200);
myAccount.withdraw(0.453);
myAccount.deposit(1000.1);
System.out.println(myAccount);
}
}
All nice answers, but they actually "miss" the "real" issue; and that is: you don't put down constructors because you can.
You create the code that is required to fulfill your requirements. In other words: you step back; and you think up respectively design the "intended use cases" for your bank account class.
And things to consider there: you avoid classes that can be created using such different paths.
Example: it is extremely dangerous to allow for empty id / owner fields. That leaves those fields with null values; and that means that you either need a lot of checking here and there; and if you forgot that here or there; you will run into NullPointerExceptions sooner or later (rather soon).
And keep in mind: your objects are meant to represent (model!) "reality". In reality, a bank account doesn't go without an owner; or an id.
In that sense, a sane implementation of your class would much more look like:
public class BankAccount {
private final String owner;
private final String id;
private double currentBalance;
public BankAccount(String owner, String id) {
this(ownwer, id, 0);
}
public BankAccount(String owner, String id, double currentBalance) {
this.owner = owner;
...
Notes here:
You want to prevent owner/id to be changed, so you make them final; and thus you also don't have setter methods for those fields
In a real world solution, you would not use Strings to represent names or ids, but distinct classes
You would also never ever use double to represent currency. This is a superbasic thing: money should not be represented using floating point numbers! (You would be looking towards to the BigDecimal class; or simply using int instead of double (and represent 1.75 $ as 175 cent)
And just a glance of real real world: nowadays, you don't model a bankaccount to have a "current balance". Instead, a bank account might be affiliated to a history of events (denoting deposits and pay-off transactions).
Final point: for your current exercise, that kind of "validation" for withdrawl/deposit is "okay"; in the "real world" validation would probably happen in many other places, too. (coming back to my initial statement: it all depends on your model; a simple model could say that a bank account itself is "validating" things; but a more realistic solution is that you have other components that deal with "rules", "laws" and all kinds of topics that do "validation").
You can do like this:
public BankAccount(String ownerName, String accountId, double accountBalance) {
this.ownerName = ownerName;
this.accountId = accountId;
this.accountBalance = accountBalance;
}
public BankAccount() {
this("some default name", "some default id", 0.0);
}
If you want to create a BankAccount with a zero balance then
public BankAccount(String ownerName, String accountId, double accountBalance) {
this.ownerName = ownerName;
this.accountId = accountId;
this.accountBalance = accountBalance;
}
public BankAccount() {
this.accountBalance = 0;
}
You can instantiate them as
BankAccount mine = new BankAccount("Scary", "123", 1000000.00);
BankAccount empty = new BankAccount();
edit
If you choose to construct a method with the second method, the id and owner would also be null and maybe not very useful
You can't create one bank account object using both constructors. You only call one of the two constructors to create a new object. So you either do:
BankAccount account = new BankAccount("Sweeper", "ABCDEF", 10000);
or:
BankAccount account = new BankAccount(100000);
Note that your second constructor's parameter is pointless because you initialize the balance to 0 no matter what the parameter is:
public BankAccount(double accountBalance) {
accountBalance = 0;
}
I think this makes more sense:
public BankAccount() {
this.accountBalance = 0;
}
As for your second question, I will give you some tips.
You need to add a new field to the class, called fee. Add getters and setters for it.
You make objects depending on which constructor you want to use. For example, to use the first constructor you need three parameters like so,
BankAccount first = new BankAccount("Bob", "ID45", 400.50);
and to use the second constructor you only need one parameter, the balance, like so,
BankAccount second = new BankAccount(400.50);
They'll both make instances of BankAccount except the difference is that on creation, the first bank account will have fields ownerName and accountId filled out while the second bank account will have those fields set to empty String values. However, the second object will have a balance of 0 unlike the first object's balance of 400.5
EDIT: Like user ScaryWombat suggested, there is a flaw in your second constructor because if you want to define an object with a balance of 0 then there's no point in adding a balance parameter. Also, in this case it would be advisable to give default values to your other fields as well,
public BankAccount() {
ownerName = "unknown";
accountId = "unknown";
accountBalance = 0;
}
So now when you create an instance of BankAccount with this constructor, it'll have the default values, "unknown", "unknown", and 0.
BankAccount third = new BankAccount();
Also, for the fee part, all you have to do is not only create another field in your BankAccount class called fee but also make a setter method to allow the user to set its fee,
private double fee;
.
.
.
public void setFee (double fee) {
this.fee = fee;
}
And in the main method, the user can access it with the following,
BankAccount account = new BankAccount("Fred", "ID145", 400);
account.setFee(15); //this will set the fee to 15

How to convert field of the list item via custom Struts type converter?

I need to implement custom conversion for ID field in Company and Employee classes. I have already implemented custom converter extended from StrutsTypeConverter and it is successfully used to convert Company.ID field, but it does not work for Employee.ID.
Seems like the main problem is in conversion properties file. How should I specify converter class for employee ID field in conversion properties file?
MyAction-conversion.properties:
company.id = com.struts2.convertors.MyCustomConverter
company.??????.id = com.struts2.convertors.MyCustomConverter
MyAction:
public class MyAction extends ActionSupport {
private Company company;
public Company getCompany () {
return company;
}
public void setCompany (Company company) {
this.company= company;
}
#Override
public String execute() {
return SUCCESS;
}
}
Company:
public class Company {
private ID id;
private List<Employee> employees;
// getters and setters
}
Employee
public class Employee{
private ID id;
private String name;
// getters and setters
}
TypeConversion Annotation:
This annotation is used for class and application wide conversion rules.
The TypeConversion annotation can be applied at property and method level.
#TypeConversion(converter = “com.test.struts2.MyConverter”)
public void setAmount(String amount)
{
this.amount = amount;
}
This annotation specifies the location of one of my converters. literally, by using this annotation, I register my class com.test.struts2.MyConverter as a converter, and gets executed every time when setAmount(String amount) method is invoked.
Try the following by adding a converter for the ID type to the xwork-conversion.properties file
com.struts2.ID = com.struts2.convertors.MyCustomConverter

how would I use an if statment to change the value of an attribute?

I want to be able to give a specific value to discount depending on certain requirements like the following age: > 25 and profession = teacher / professor get 10% discount, age < 25 and gradepoint > 7 get 25% discount
this is my code so far I am using double OO paradigm:
public class customer {
//attribute definitions
private String name;
private String address;
private String profession;
private Integer age;
private Integer gradepoint;
private double discount;
//constructor
public customer(String newName, String newAddress, String newProfession, Integer newAge, Integer newGradepoint, double newDiscount)
{
setName(newName);
setAddress(newAddress);
setProfession(newProfession);
setAge(newAge);
setGradepoint(newGradepoint);
setDiscount (newDiscount);
}
//getters
public String getName()
{ return name;}
public String getAddress()
{ return address;}
public String getProfession()
{ return profession;}
public Integer getAge()
{ return age;}
public Integer getGradepoint()
{ return gradepoint;}
public double getDiscount()
{ return discount;}
//setters
public void setName (String newName)
{ name = newName;}
public void setAddress (String newAddress)
{ address = newAddress;}
public void setProfession (String newProfession)
{ profession = newProfession;}
public void setAge (Integer newAge)
{ age = newAge;}
public void setGradepoint (Integer newGradepoint)
{ gradepoint = newGradepoint;}
public void setDiscount (double newDiscount)
{ discount = newDiscount;}
//methods
}
Would I need to create a sub class called discount or each type of discount? or I can write a method directly into this customer class to control the discount?
write a method directly into this customer class to control the discount?
This. Make it a calculated field. Kill setDiscount function, kill discount variable, and make the getDiscount function into something like:
public double getDiscount() {
if (...) return ...;
if (....) return ...;
...
}
...unless you want to have this as the default discount, and still allow modification, in which case keep discount as a property, and move this whole logic into the constructor, having conditional setDiscount() calls.
Your getDiscount function would ideally do the calculation and return the appropriate discount for the current object. For example:
public double getDiscount()
{
if (getAge() < 25 && getGradepoint() > 7)
{
return .25;
}
else if // other logic...
}
Although not the simplest solution, I would abstract the discount calculation to a separate interface and class as well as having an override discount value in the customer object.
E.g.
public interface DiscountManager<T>
{
public double getDiscount(T discountObject);
}
public abstract class AbstractCustomerDiscountManager extends DiscountManager<Customer>
{
public double getDiscount(Customer customer)
{
if (customer.hasCustomDiscount()) { return customer.getDiscount(); }
else { return calculateDiscount(customer); }
}
public abstract double calculateDiscount(Customer customer);
}
public class DefaultDiscountManager extends AbstractCustomerDiscountManager
{
public double calculateDiscount(Customer customer)
{
double discount = 0;
if ((customer.getAge() != null) && (customer.getAge() < 25)) { discount += 25; }
...
return discount;
}
}
Probably over time different rules evolve. At the spot where the discounting takes place, in the order, the discount and and a reference to the rule applied should be stored together.
This kind of business logic could have its own class. A generic solution would even be to store the rule as scriptable code (BeanShell = Java, or JavaScript) and use java's scripting API. So that this kind of business logic resides more with the business managers, and the rules can be presented and edited.

Categories