Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement portfolio batch service #92

Merged
merged 6 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ void setUp() {
int lastYear = LocalDate.now().getYear() - 1;
Instant exDividendDate = LocalDate.now().minusYears(1).plusDays(1).atStartOfDay().toInstant(UTC);
Stock appl = StockFixture.createStock(AAPL, Sector.TECHNOLOGY, 2.0);
Dividend dividend = DividendFixture.createDividendWithPaymentDate(appl.getId(), 0.5, exDividendDate);
Dividend dividend = DividendFixture.createDividendWithExDividendDate(appl.getId(), 0.5, exDividendDate);

given(stockRepository.findByTicker(any())).willReturn(Optional.of(appl));
given(dividendRepository.findAllByStockId(any())).willReturn(List.of(dividend));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ class StockControllerTest extends IntegrationTest {
Double price = null;
Double dividend = 12.0;
Stock tsla = stockRepository.save(StockFixture.createStock(TSLA, Sector.CONSUMER_CYCLICAL, price));
Instant paymentDate = LocalDate.of(2023, 4, 5).atStartOfDay().toInstant(UTC);
dividendRepository.save(DividendFixture.createDividendWithPaymentDate(tsla.getId(), dividend, paymentDate));
dividendRepository.save(DividendFixture.createDividendWithDividend(tsla.getId(), dividend));

// when, then
StockDetailResponse stockDetailResponse = RestAssured
Expand All @@ -210,9 +209,7 @@ class StockControllerTest extends IntegrationTest {

assertAll(
() -> assertThat(stockDetailResponse.dividendPerShare()).isEqualTo(dividend),
() -> assertThat(stockDetailResponse.dividendYield()).isEqualTo(0),
() -> assertThat(stockDetailResponse.earliestPaymentDate()).isEqualTo(LocalDate.of(LocalDate.now().getYear(), 4, 5)),
() -> assertThat(stockDetailResponse.dividendMonths()).isEqualTo(List.of(Month.APRIL))
() -> assertThat(stockDetailResponse.dividendYield()).isEqualTo(0)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import nexters.payout.batch.application.FinancialClient.DividendData;
import nexters.payout.batch.application.client.FinancialClient;
import nexters.payout.batch.application.client.FinancialClient.DividendData;
import nexters.payout.domain.dividend.domain.Dividend;
import nexters.payout.domain.dividend.application.DividendCommandService;
import nexters.payout.domain.stock.domain.Stock;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nexters.payout.batch.application;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import nexters.payout.domain.portfolio.domain.Portfolio;
import nexters.payout.domain.portfolio.domain.repository.PortfolioRepository;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.List;
import java.util.UUID;

@Slf4j
@RequiredArgsConstructor
@Service
@Transactional
public class PortfolioBatchService {

private final PortfolioRepository portfolioRepository;

@Scheduled(cron = "${schedules.cron.portfolio}", zone = "UTC")
void deletePortfolio() {
log.info("delete portfolio start..");
portfolioRepository.deleteAllByIdInQuery(getExpiredPortfolioIds());
log.info("delete portfolio end..");
}

private List<UUID> getExpiredPortfolioIds() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expired_at column을 이용해서 유효기간이 지난 포트폴리오를 제거하는 쿼리를 jpa에서 한번에 짜는 방식 (deleteAllByExpiredAftter~~)도 가능할 것 같은데 id 리스트 먼저 찾도록 하신 이유가 있으실까요???

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jpa에서 deleteAllBy~ 를 사용하면 건 별로 삭제 쿼리가 날라가는 것으로 알고 있어서 deleteAllById를 사용하였는데 이것도 건 별로 삭제 쿼리가 날라가더라구요..?(방금 알았어요 😂)
jpql 직접 생성하는 것으로 변경해놓겠습니다 👍

return portfolioRepository.findByExpireAtBefore(Instant.now())
.stream()
.map(Portfolio::getId)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import nexters.payout.batch.application.FinancialClient.StockData;
import nexters.payout.batch.application.client.FinancialClient;
import nexters.payout.batch.application.client.FinancialClient.StockData;
import nexters.payout.batch.application.client.StockLogo;
import nexters.payout.domain.stock.application.StockCommandService;
import nexters.payout.domain.stock.domain.repository.StockRepository;
import org.springframework.scheduling.annotation.Scheduled;
Expand All @@ -24,7 +26,7 @@ public class StockBatchService {
* UTC 시간대 기준 매일 자정에 모든 종목의 현재가와 거래량을 업데이트합니다.
*/
@Scheduled(cron = "${schedules.cron.stock}", zone = "UTC")
void run() {
void updateStock() {
log.info("update stock start..");
List<StockData> stockList = financialClient.getLatestStockList();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nexters.payout.batch.application;
package nexters.payout.batch.application.client;

import nexters.payout.domain.stock.domain.Sector;
import nexters.payout.domain.stock.domain.Stock;
Expand All @@ -24,11 +24,11 @@ record StockData(
Integer volume,
Integer avgVolume
) {
Stock toDomain() {
public Stock toDomain() {
return new Stock(ticker, name, sector, exchange, industry, price, volume, null);
}

Stock toDomain(String logoUrl) {
public Stock toDomain(String logoUrl) {
return new Stock(ticker, name, sector, exchange, industry, price, volume, logoUrl);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nexters.payout.batch.application;
package nexters.payout.batch.application.client;

public interface StockLogo {
String getLogoUrl(String ticker);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import lombok.Getter;
import lombok.Setter;
import nexters.payout.batch.application.FinancialClient.DividendData;
import nexters.payout.batch.application.client.FinancialClient.DividendData;
import nexters.payout.core.time.DateFormat;

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nexters.payout.batch.infra.fmp;

import lombok.extern.slf4j.Slf4j;
import nexters.payout.batch.application.FinancialClient;
import nexters.payout.batch.application.client.FinancialClient;
import nexters.payout.core.time.DateFormat;
import nexters.payout.core.time.InstantProvider;
import nexters.payout.domain.stock.domain.Exchange;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package nexters.payout.batch.infra.ninjas;

import lombok.extern.slf4j.Slf4j;
import nexters.payout.batch.application.StockLogo;
import nexters.payout.batch.application.client.StockLogo;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

Expand Down
1 change: 1 addition & 0 deletions batch/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ financial:
schedules:
cron:
stock: "0 0 3 * * *"
portfolio: "-"
dividend:
past: "0 0 4 * * 0"
future: "0 0 4 * * *"
1 change: 1 addition & 0 deletions batch/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ spring:
schedules:
cron:
stock: "0 0 2 * * *"
portfolio: "0 0 0 * * *"
dividend:
past: "0 0 4 * * 0"
future: "0 0 4 * * *"
Expand Down
1 change: 1 addition & 0 deletions batch/src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ spring:
schedules:
cron:
stock: "-"
portfolio: "-"
dividend:
past: "-"
future: "-"
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nexters.payout.batch.application;

import nexters.payout.batch.application.client.FinancialClient;
import nexters.payout.batch.common.AbstractBatchServiceTest;
import nexters.payout.domain.DividendFixture;
import nexters.payout.domain.StockFixture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import nexters.payout.domain.stock.domain.Exchange;
import nexters.payout.domain.stock.domain.Sector;
import nexters.payout.batch.application.FinancialClient.StockData;
import nexters.payout.batch.application.client.FinancialClient.StockData;

public class LatestStockFixture {
public static StockData createStockData(String ticker, Double price, Integer volume) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package nexters.payout.batch.application;

import nexters.payout.batch.common.AbstractBatchServiceTest;
import nexters.payout.domain.PortfolioFixture;
import nexters.payout.domain.portfolio.domain.Portfolio;
import nexters.payout.domain.portfolio.domain.PortfolioStock;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;

import static nexters.payout.domain.PortfolioFixture.STOCK_ID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

class PortfolioBatchServiceTest extends AbstractBatchServiceTest {

@Test
void 만료기간이_지난_포트폴리오는_삭제한다() {
// given
portfolioRepository.save(PortfolioFixture.createPortfolio(
Instant.now().minus(1, ChronoUnit.DAYS),
List.of(new PortfolioStock(STOCK_ID, 1))
));
portfolioRepository.save(PortfolioFixture.createPortfolio(
Instant.now().minus(2, ChronoUnit.DAYS),
List.of(new PortfolioStock(STOCK_ID, 2))
));
Portfolio notExpiredPortfolio = portfolioRepository.save(PortfolioFixture.createPortfolio(
Instant.now().plus(1, ChronoUnit.DAYS),
List.of(new PortfolioStock(STOCK_ID, 1))
));

// when
portfolioBatchService.deletePortfolio();

// then
List<Portfolio> actual = portfolioRepository.findAll();
assertAll(
() -> assertThat(actual).hasSize(1),
() -> assertThat(actual.get(0)).isEqualTo(notExpiredPortfolio)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nexters.payout.batch.application;

import nexters.payout.batch.application.client.FinancialClient;
import nexters.payout.batch.common.AbstractBatchServiceTest;
import nexters.payout.domain.StockFixture;
import nexters.payout.domain.stock.domain.Stock;
Expand All @@ -23,7 +24,7 @@ class StockBatchServiceTest extends AbstractBatchServiceTest {
given(financialClient.getLatestStockList()).willReturn(List.of(stockData));

// when
stockBatchService.run();
stockBatchService.updateStock();

// then
Stock actual = stockRepository.findByTicker(stock.getTicker()).get();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package nexters.payout.batch.common;

import nexters.payout.batch.application.DividendBatchService;
import nexters.payout.batch.application.FinancialClient;
import nexters.payout.batch.application.PortfolioBatchService;
import nexters.payout.batch.application.client.FinancialClient;
import nexters.payout.batch.application.StockBatchService;
import nexters.payout.domain.dividend.domain.repository.DividendRepository;
import nexters.payout.domain.portfolio.domain.repository.PortfolioRepository;
import nexters.payout.domain.stock.domain.repository.StockRepository;
import org.junit.jupiter.api.AfterEach;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -23,14 +25,21 @@ public abstract class AbstractBatchServiceTest {
@Autowired
public DividendRepository dividendRepository;

@Autowired
public PortfolioRepository portfolioRepository;

@Autowired
public StockBatchService stockBatchService;

@Autowired
public DividendBatchService dividendBatchService;

@Autowired
public PortfolioBatchService portfolioBatchService;

@AfterEach
void afterEach() {
portfolioRepository.deleteAll();
dividendRepository.deleteAll();
stockRepository.deleteAll();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package nexters.payout.domain.portfolio.domain;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.*;
import lombok.Getter;
import nexters.payout.domain.BaseEntity;

Expand All @@ -17,23 +14,18 @@
@Getter
public class Portfolio extends BaseEntity {

@ElementCollection
@CollectionTable(name = "portfolio_stock", joinColumns = @JoinColumn(name = "portfolio_id"))
private List<PortfolioStock> stocks = new ArrayList<>();
@Embedded
private PortfolioStocks portfolioStocks;

private Instant expireAt;

public Portfolio() {
super(null);
}

public Portfolio(final UUID id, final Instant expireAt) {
super(id);
this.expireAt = expireAt;
}

public Portfolio(final Instant expireAt, List<PortfolioStock> stocks) {
super(null);
this.stocks = stocks;
this.portfolioStocks = new PortfolioStocks(stocks);
this.expireAt = expireAt;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,8 @@ public class PortfolioStock {
private UUID stockId;
private Integer shares;


public PortfolioStock(final UUID id, final UUID stockId, final Integer shares) {
public PortfolioStock(final UUID stockId, final Integer shares) {
this.stockId = stockId;
this.shares = shares;
}

private PortfolioStock(final UUID stockId, final Integer shares) {
this.stockId = stockId;
this.shares = shares;
}

public PortfolioStock create(final UUID portfolioId, final UUID stockId, final Integer shares) {
return new PortfolioStock(portfolioId, stockId, shares);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package nexters.payout.domain.portfolio.domain;

import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.JoinColumn;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@NoArgsConstructor
@Embeddable
public class PortfolioStocks {

@ElementCollection
@CollectionTable(name = "portfolio_stock", joinColumns = @JoinColumn(name = "portfolio_id"))
private List<PortfolioStock> portfolioStocks = new ArrayList<>();

public PortfolioStocks(List<PortfolioStock> stocks) {
if (stocks.isEmpty()) {
throw new IllegalArgumentException("portfolioStocks must not be empty");
}
portfolioStocks = stocks;
}

public List<PortfolioStock> getPortfolioStocks() {
return Collections.unmodifiableList(portfolioStocks);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

import nexters.payout.domain.portfolio.domain.Portfolio;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.time.Instant;
import java.util.List;
import java.util.UUID;

public interface PortfolioRepository extends JpaRepository<Portfolio, UUID> {
List<Portfolio> findByExpireAtBefore(Instant date);

@Modifying(clearAutomatically = true)
@Query("delete from Portfolio p where p.id in :ids")
void deleteAllByIdInQuery(List<UUID> ids);
}
Loading
Loading