Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ out/

### 로컬 환경변수 ###
local.properties
/logs
/logs
docker-compose.override.yml
5 changes: 3 additions & 2 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ services:
volumes:
- ../build/libs/coin-0.0.1-SNAPSHOT.jar:/app/coin-0.0.1-SNAPSHOT.jar
- /etc/localtime:/etc/localtime:ro
- /home/ubuntu/logs/springboot:/app/logs
working_dir: /app
command: ["java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,mariadb-local"]
command: ["java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,it,mariadb-local"]
ports:
- "8080:8080"
- "5005:5005"
env_file:
- ./local.properties
environment:
- TZ=Asia/Seoul
- JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
depends_on:
mariadb:
condition: service_healthy
Expand Down
6 changes: 6 additions & 0 deletions docker/mariadb/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ create table account
user_id int not null
);

create or replace index idx_account_user_id
on account (user_id);

create table asset
(
ticker varchar(10) not null
Expand Down Expand Up @@ -91,6 +94,9 @@ create table wallet
ticker varchar(10) not null
);

create or replace index idx_wallet_account_id_ticker
on wallet (account_id, ticker);



INSERT INTO `if`.users (user_id, created_at) VALUES (1, '2025-05-16 09:30:00.000000');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.cleanengine.coin.common.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Aspect
@Component
public class TransactionLoggingAspect {

private static final String TRANSACTION_ID_KEY = "txId";

@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object logTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
String txId = UUID.randomUUID().toString().substring(0, 8);
MDC.put(TRANSACTION_ID_KEY, txId);

try {
return joinPoint.proceed();
} finally {
MDC.remove(TRANSACTION_ID_KEY);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.cleanengine.coin.configuration.bootstrap;

import com.cleanengine.coin.order.domain.Asset;
import com.cleanengine.coin.order.adapter.out.persistentce.wallet.OrderWalletRepository;
import com.cleanengine.coin.order.adapter.out.persistentce.asset.AssetRepository;
import com.cleanengine.coin.order.domain.Asset;
import com.cleanengine.coin.user.domain.Account;
import com.cleanengine.coin.user.domain.User;
import com.cleanengine.coin.user.domain.Wallet;
import com.cleanengine.coin.user.info.infra.AccountRepository;
import com.cleanengine.coin.user.info.infra.UserRepository;
import com.cleanengine.coin.user.info.infra.WalletRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
Expand All @@ -24,7 +24,7 @@
public class DBInitRunner implements CommandLineRunner {
private final AccountRepository accountRepository;
private final UserRepository userRepository;
private final OrderWalletRepository orderWalletRepository;
private final WalletRepository walletRepository;
private final AssetRepository assetRepository;

@Transactional
Expand Down Expand Up @@ -58,7 +58,7 @@ protected void initSellBotData(){
wallet2.setTicker("TRUMP");
wallet2.setAccountId(account.getId());
wallet2.setSize(500_000_000.0);
orderWalletRepository.saveAll(List.of(wallet, wallet2));
walletRepository.saveAll(List.of(wallet, wallet2));
}

@Transactional
Expand All @@ -80,7 +80,7 @@ protected void initBuyBotData() {
wallet2.setTicker("TRUMP");
wallet2.setAccountId(account.getId());
wallet2.setSize(0.0);
orderWalletRepository.saveAll(List.of(wallet, wallet2));
walletRepository.saveAll(List.of(wallet, wallet2));
}

@Transactional
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public List<AssetInfo> getAllAssetInfos(){
return assetRepository.findAll().stream().map(AssetInfo::from).toList();
}

public List<String> getAllAssetTickers(){
return assetRepository.findAll().stream().map(Asset::getTicker).toList();
}

public boolean isAssetExist(String ticker){
if(assetCacheRepository.isAssetExists(ticker)) return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.validation.Validator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
Expand All @@ -22,11 +23,11 @@
@Service
@RequiredArgsConstructor
@Validated
public class OrderService {
public class OrderService {
private final List<CreateOrderStrategy<?, ?>> createOrderStrategies;
private final Validator validator;

@Transactional
@Transactional(isolation = Isolation.READ_COMMITTED)
public OrderInfo<?> createOrder(OrderCommand.CreateOrder createOrder){
validateCreateOrder(createOrder);
CreateOrderStrategy<?, ?> createOrderStrategy = createOrderStrategies.stream()
Expand All @@ -35,14 +36,14 @@ public OrderInfo<?> createOrder(OrderCommand.CreateOrder createOrder){
return createOrderStrategy.processCreatingOrder(createOrder);
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public OrderInfo<?> createOrderWithBot(String ticker, Boolean isBuyOrder, Double orderSize, Double price){
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void createOrderWithBot(String ticker, Boolean isBuyOrder, Double orderSize, Double price){
Integer userId = isBuyOrder? BUY_ORDER_BOT_ID : SELL_ORDER_BOT_ID;

OrderCommand.CreateOrder createOrder = new OrderCommand.CreateOrder(ticker, userId, isBuyOrder,
false, orderSize, price, LocalDateTime.now(), true);

return createOrder(createOrder);
createOrder(createOrder);
}

protected void validateCreateOrder(OrderCommand.CreateOrder createOrder) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.cleanengine.coin.order.application.strategy;

import com.cleanengine.coin.common.error.DomainValidationException;
import com.cleanengine.coin.order.adapter.out.persistentce.account.OrderAccountRepository;
import com.cleanengine.coin.order.adapter.out.persistentce.order.command.BuyOrderRepository;
import com.cleanengine.coin.order.adapter.out.persistentce.wallet.OrderWalletRepository;
import com.cleanengine.coin.order.application.AssetService;
import com.cleanengine.coin.order.application.dto.OrderInfo;
import com.cleanengine.coin.order.application.port.out.PublishOrderCreatedPort;
Expand All @@ -12,6 +10,8 @@
import com.cleanengine.coin.order.domain.domainservice.CreateBuyOrderDomainService;
import com.cleanengine.coin.order.domain.domainservice.CreateOrderDomainService;
import com.cleanengine.coin.user.domain.Account;
import com.cleanengine.coin.user.info.infra.AccountRepository;
import com.cleanengine.coin.user.info.infra.WalletRepository;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;

Expand Down Expand Up @@ -57,11 +57,11 @@ protected OrderInfo.BuyOrderInfo extractOrderInfo(Order order) {

public BuyOrderStrategy(PublishOrderCreatedPort publishOrderCreatedPort,
AssetService assetService,
OrderWalletRepository orderWalletRepository,
OrderAccountRepository orderAccountRepository,
WalletRepository walletRepository,
AccountRepository accountRepository,
BuyOrderRepository buyOrderRepository,
CreateBuyOrderDomainService createOrderDomainService) {
super(publishOrderCreatedPort, assetService, orderWalletRepository, orderAccountRepository);
super(publishOrderCreatedPort, assetService, walletRepository, accountRepository);
this.buyOrderRepository = buyOrderRepository;
this.createOrderDomainService = createOrderDomainService;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package com.cleanengine.coin.order.application.strategy;

import com.cleanengine.coin.common.error.DomainValidationException;
import com.cleanengine.coin.order.adapter.out.persistentce.account.OrderAccountRepository;
import com.cleanengine.coin.order.adapter.out.persistentce.wallet.OrderWalletRepository;
import com.cleanengine.coin.order.application.AssetService;
import com.cleanengine.coin.order.application.dto.OrderCommand;
import com.cleanengine.coin.order.application.dto.OrderInfo;
import com.cleanengine.coin.order.application.event.OrderCreated;
import com.cleanengine.coin.order.application.port.out.PublishOrderCreatedPort;
import com.cleanengine.coin.order.domain.Order;
import com.cleanengine.coin.order.domain.domainservice.CreateOrderDomainService;
import com.cleanengine.coin.user.domain.Account;
import com.cleanengine.coin.user.domain.Wallet;
import com.cleanengine.coin.user.info.infra.AccountRepository;
import com.cleanengine.coin.user.info.infra.WalletRepository;
import lombok.AllArgsConstructor;
import org.springframework.validation.FieldError;

Expand All @@ -21,14 +19,13 @@
public abstract class CreateOrderStrategy<T extends Order, S extends OrderInfo<?>> {
protected final PublishOrderCreatedPort publishOrderCreatedPort;
protected final AssetService assetService;
protected final OrderWalletRepository walletRepository;
protected final OrderAccountRepository accountRepository;
protected final WalletRepository walletRepository;
protected final AccountRepository accountRepository;

public S processCreatingOrder(OrderCommand.CreateOrder createOrderCommand){
validateTicker(createOrderCommand.ticker());
T order = createOrder(createOrderCommand);
saveOrder(order);
createWalletIfNeeded(order.getUserId(), order.getTicker());
keepHoldings(order);
publishOrderCreatedPort.publish(new OrderCreated(order));
return extractOrderInfo(order);
Expand All @@ -49,21 +46,12 @@ protected void validateTicker(String ticker){
}

protected T createOrder(OrderCommand.CreateOrder createOrderCommand){
T order = createOrderDomainService().createOrder(

return createOrderDomainService().createOrder(
createOrderCommand.ticker(), createOrderCommand.userId(),
createOrderCommand.isBuyOrder(), createOrderCommand.isMarketOrder(),
createOrderCommand.orderSize(), createOrderCommand.price(),
createOrderCommand.createdAt(), createOrderCommand.isBot());

return order;
}

// TODO 책임이 너무 많은
protected void createWalletIfNeeded(Integer userId, String ticker){
if(walletRepository.findWalletBy(userId, ticker).isEmpty()){
Account account = accountRepository.findByUserId(userId).orElseThrow();
Wallet wallet = Wallet.generateEmptyWallet(ticker, account.getId());
walletRepository.save(wallet);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package com.cleanengine.coin.order.application.strategy;

import com.cleanengine.coin.common.error.DomainValidationException;
import com.cleanengine.coin.order.adapter.out.persistentce.account.OrderAccountRepository;
import com.cleanengine.coin.order.adapter.out.persistentce.order.command.SellOrderRepository;
import com.cleanengine.coin.order.adapter.out.persistentce.wallet.OrderWalletRepository;
import com.cleanengine.coin.order.application.AssetService;
import com.cleanengine.coin.order.application.dto.OrderInfo;
import com.cleanengine.coin.order.application.port.out.PublishOrderCreatedPort;
Expand All @@ -12,6 +10,8 @@
import com.cleanengine.coin.order.domain.domainservice.CreateOrderDomainService;
import com.cleanengine.coin.order.domain.domainservice.CreateSellOrderDomainService;
import com.cleanengine.coin.user.domain.Wallet;
import com.cleanengine.coin.user.info.infra.AccountRepository;
import com.cleanengine.coin.user.info.infra.WalletRepository;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;

Expand Down Expand Up @@ -39,7 +39,7 @@ protected void keepHoldings(SellOrder order) throws RuntimeException {
Double orderSize = order.getOrderSize();

Wallet wallet = walletRepository
.findWalletBy(userId, ticker)
.findByAccountIdAndTicker(userId, ticker)
.orElseThrow(()->
new DomainValidationException("Wallet not found",
List.of(new FieldError("wallet", "userId", "user might not exist"),
Expand All @@ -62,11 +62,11 @@ protected OrderInfo.SellOrderInfo extractOrderInfo(Order order) {

public SellOrderStrategy(PublishOrderCreatedPort publishOrderCreatedPort,
AssetService assetService,
OrderWalletRepository orderWalletRepository,
OrderAccountRepository orderAccountRepository,
WalletRepository walletRepository,
AccountRepository accountRepository,
SellOrderRepository sellOrderRepository,
CreateSellOrderDomainService createOrderDomainService) {
super(publishOrderCreatedPort, assetService, orderWalletRepository, orderAccountRepository);
super(publishOrderCreatedPort, assetService, walletRepository, accountRepository);
this.sellOrderRepository = sellOrderRepository;
this.createOrderDomainService = createOrderDomainService;
}
Expand Down
Loading
Loading