ํ์ : ๊ฐ์ธ ํ๋ก์ ํธ
๊ธฐ๊ฐ : 2025.01 ~ ์งํ ์ค
๋งํฌ : https://github.com/M-ung/TicToc_Server
์๋น์ค ๋ด์ฉ : ๋น์ ์ ์๊ฐ์ ๊ฐ์น๋ฅผ ๋งค๊ธฐ๋ค, ์๊ฐ ๊ฑฐ๋ ๊ฒฝ๋งค ํ๋ซํผ
๊ฒฝ๋งค ๊ด๋ จ ๋น์ง๋์ค ๋ก์ง์ ๊ตฌํํ๋ฉด์ ๊ณ ๋ฏผ์ด ์๊ฒผ๋ค. ์๋ฅผ ๋ค์ด "๊ฒฝ๋งค ์์ " ๊ธฐ๋ฅ์ ๊ตฌํํ๋ค๊ณ ๊ฐ์ ํด ๋ณด์. ๊ทธ๋ผ "๊ฒฝ๋งค ์์ " ์ ์ ์ฌ์ฉ์์ userId์ ๊ฒฝ๋งค์ userId๊ฐ ๋์ผํ ์ง ํ์ธํด์ผ ํ๋ ๋ก์ง์ด ํ์ํ๋ค. ์ด ์ธ์๋ ์ ํจ์ฑ ๊ฒ์ฌ, ๊ฐ์ฒด ์์ ๊ณผ ๊ฐ์ ์ฌ๋ฌ ๊ธฐ๋ฅ๋ค์ด ํ์ํ ๋ Service ๊ณ์ธต์ ๋ฉ์๋๋ฅผ ๊ณ์ ๋ง๋ค์ด์๋ค.
๊ทธ๋ ๊ฒ ๋๋ฉด Service ์ฝ๋๋ ์๋์ ๊ฐ๋ค. (์๋ ์ฝ๋๋ ๋ฉํฐ๋ชจ๋ + ํฅ์ฌ๊ณ ๋ ์ํคํ
์ฒ ์ ์ฉ ์ ์ฝ๋์ด๋ค.)
@Service
@Transactional
@RequiredArgsConstructor
public class AuctionCommandServiceImpl implements AuctionCommandService {
private final AuctionRepository auctionRepository;
private final AuctionHistoryRepository auctionHistoryRepository;
@Override
public void register(final Long userId, AuctionRequestDTO.Register requestDTO) {
checkAuctionTimeRange(userId, requestDTO.sellStartTime(), requestDTO.sellEndTime());
auctionRepository.save(Auction.of(userId, requestDTO));
}
@Override
public void update(final Long userId, final Long auctionId, AuctionRequestDTO.Update requestDTO) {
validateAuctionAccess(userId, auctionId);
checkAuctionTimeRange(userId, requestDTO.sellStartTime(), requestDTO.sellEndTime());
try {
findAuctionById(auctionId).update(requestDTO);
} catch (OptimisticLockingFailureException e) {
throw new ConflictAuctionUpdateException(CONFLICT_AUCTION_UPDATE);
}
}
@Override
public void delete(final Long userId, final Long auctionId) {
validateAuctionAccess(userId, auctionId);
try {
findAuctionById(auctionId).deactivate();
} catch (OptimisticLockingFailureException e) {
throw new ConflictAuctionDeleteException(CONFLICT_AUCTION_DELETE);
}
}
private Auction findAuctionById(final Long auctionId) {
return auctionRepository.findById(auctionId)
.orElseThrow(() -> new AuctionNotFoundException(AUCTION_NOT_FOUND));
}
private void validateAuctionAccess(final Long userId, final Long auctioneerId) {
if(!userId.equals(auctioneerId)) {
throw new AuctionNoAccessException(AUCTION_NO_ACCESS);
}
}
private void checkAuctionTimeRange(Long userId, LocalDateTime sellStartTime, LocalDateTime sellEndTime) {
if(auctionRepository.existsAuctionInTimeRange(userId, sellStartTime, sellEndTime)) {
throw new DuplicateAuctionDateException(DUPLICATE_AUCTION_DATE);
}
}
}
์ ์ฝ๋๋ ๊ฐ๋ฐ์ด ๋๋ ์ํ๊ฐ ์๋, ๊ฐ๋ฐ ์ค์ธ ์ํ์ด๋ค. ๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ validateAuctionAccess
์ ๊ฐ์ ๋ฉ์๋๊ฐ ๋ณต์ก์ฑ์ ์ฆ๊ฐ์ํค๊ณ ์์ผ๋ฉฐ ํด๋น ๋ฉ์๋๋ ๋ค๋ฅธ Service ๊ณ์ธต์์๋ ํ์ํ๊ธฐ์ ์ฝ๋ ์ค๋ณต์ฑ ๋ํ ์ผ์ผ์ผฐ๋ค.
๋ฌธ์ ๋ฅผ ์ ๋ฆฌํ๋ฉด ์๋์ ๊ฐ๋ค.
1. Service ๊ณ์ธต์ ์ฑ
์ ์ฆ๊ฐ
2. ์ฝ๋ ๋ณต์ก์ฑ ์ฆ๊ฐ
3. ์ฝ๋ ์ค๋ณต์ฑ ์ฆ๊ฐ
์ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด์ findAuctionById
์ ๊ฐ์ด DB๋ฅผ ์ ๊ทผํด์ผ ํ๋ ๋ฉ์๋๊ฐ ์๋๋ผ๋ฉด ๋๋ฉ์ธ์ ๋ฉ์๋๋ฅผ ์์ฑํ๋ "์บก์ํ" ๋ฐฉ์์ ํํ๋ค.
1. ๋๋ฉ์ธ ์บก์ํ ๋ฉ์๋ ์ ์ฉ
2. Service ๊ณ์ธต์ ๋จ์ํ
1. ๋๋ฉ์ธ ์บก์ํ ๋ฉ์๋ ์ ์ฉ
DB ์ ๊ทผ์ด ํ์ํ์ง ์์ ๋๋ฉ์ธ์ ๋น์ง๋์ค ๋ก์ง์ ๋๋ฉ์ธ ๊ณ์ธต์ ์บก์ํํ์ฌ ๋ฉ์๋๋ฅผ ์์ฑํ๋ค.
์ด์ ์๋ Service ๊ณ์ธต์์ ์ง์ ๊ฒ์ฆ ๋ก์ง๊ณผ ์ํ ๋ณ๊ฒฝ์ ์ํํ์ง๋ง, ๊ฐ์ฒด ์งํฅ์ ์ธ ์ค๊ณํ์ฌ ๋ณต์ก์ฑ๊ณผ ์ค๋ณต์ฑ์ ์ค์ด๊ธฐ ์ํด ๋๋ฉ์ธ ๊ฐ์ฒด๊ฐ ์ค์ค๋ก ๋น์ฆ๋์ค ๋ก์ง์ ์ํํ๋๋ก ๋ณ๊ฒฝํ๋ค.
@Getter
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "auction")
public class Auction extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long auctioneerId;
private String title;
@Column(nullable = false, columnDefinition = "TEXT")
private String content;
private Integer startPrice;
private Integer currentPrice;
private Integer finalPrice;
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime sellStartTime; // ํ๋งคํ๊ณ ์ถ์ ์๊ฐ(์์)
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime sellEndTime; // ํ๋งคํ๊ณ ์ถ์ ์๊ฐ(์ข
๋ฃ)
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime auctionOpenTime; // ๊ฒฝ๋งค ์์ ์๊ฐ
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime auctionCloseTime; // ๊ฒฝ๋งค ์ข
๋ฃ ์๊ฐ
@Enumerated(EnumType.STRING)
private AuctionProgress progress;
@Enumerated(EnumType.STRING)
private AuctionType type;
@Enumerated(EnumType.STRING)
private TicTocStatus status;
public static Auction of(final Long userId, AuctionUseCaseReqDTO.Register requestDTO) {
return Auction.builder()
.auctioneerId(userId)
.title(requestDTO.title())
.content(requestDTO.content())
.startPrice(requestDTO.startPrice())
.currentPrice(requestDTO.startPrice())
.finalPrice(requestDTO.startPrice())
.sellStartTime(requestDTO.sellStartTime())
.sellEndTime(requestDTO.sellEndTime())
.auctionOpenTime(LocalDateTime.now())
.auctionCloseTime(requestDTO.auctionCloseTime())
.progress(NOT_STARTED)
.type(requestDTO.type())
.status(TicTocStatus.ACTIVE)
.build();
}
public void update(AuctionUseCaseReqDTO.Update requestDTO) {
validateAuctionAlreadyStarted();
this.title = requestDTO.title();
this.content = requestDTO.content();
this.startPrice = requestDTO.startPrice();
this.currentPrice = requestDTO.startPrice();
this.finalPrice = requestDTO.startPrice();
this.sellStartTime = requestDTO.sellStartTime();
this.sellEndTime = requestDTO.sellEndTime();
this.auctionCloseTime = requestDTO.auctionCloseTime();
this.type = requestDTO.type();
}
public void deactivate(final Long userId) {
validateAuctionAccess(userId);
validateAuctionAlreadyStarted();
this.status = TicTocStatus.DISACTIVE;
}
public void validateAuctionAccess(final Long userId) {
if(!userId.equals(this.auctioneerId)) {
throw new BidNoAccessException(BID_NO_ACCESS);
}
}
private void validateAuctionAlreadyStarted() {
if(!this.getProgress().equals(NOT_STARTED)) {
throw new AuctionAlreadyStartedException(AUCTION_ALREADY_STARTED);
}
}
}
2. Service ๊ณ์ธต์ ๋จ์ํ
Service ๊ณ์ธต์์๋ DB๋ฅผ ์ ๊ทผํ๋ ๋ก์ง๋ง ์ฒ๋ฆฌํ๊ณ , ๋น์ฆ๋์ค ๋ก์ง์ ๋๋ฉ์ธ ๊ฐ์ฒด์์ ์ํํ๋๋ก ๋ณ๊ฒฝํ์ฌ ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ์ญํ ๋ง ๋ด๋นํ๋๋ก ๊ตฌํํ๋ค.
@Service
@Transactional
@RequiredArgsConstructor
public class AuctionCommandService implements AuctionCommandUseCase {
private final LocationCommandUseCase locationCommandUseCase;
private final AuctionRepositoryPort auctionRepositoryPort;
private final CloseAuctionUseCase closeAuctionUseCase;
@Override
public void register(final Long userId, AuctionUseCaseReqDTO.Register requestDTO) {
auctionRepositoryPort.validateAuctionTimeRangeForSave(userId, requestDTO.sellStartTime(), requestDTO.sellEndTime());
var auction = auctionRepositoryPort.saveAuction(Auction.of(userId, requestDTO));
var auctionId = auction.getId();
if (!requestDTO.type().equals(AuctionType.ONLINE)) {
locationCommandUseCase.saveAuctionLocations(auctionId, requestDTO.locations());
}
closeAuctionUseCase.save(auctionId, auction.getAuctionCloseTime());
}
@Override
public void update(final Long userId, final Long auctionId, AuctionUseCaseReqDTO.Update requestDTO) {
var findAuction = auctionRepositoryPort.findAuctionByIdForUpdate(auctionId);
findAuction.validateAuctionAccess(userId);
auctionRepositoryPort.validateAuctionTimeRangeForUpdate(userId, auctionId, findAuction.getSellStartTime(), findAuction.getSellEndTime());
try {
findAuction.update(requestDTO);
if(!findAuction.getType().equals(AuctionType.ONLINE)) {
locationCommandUseCase.deleteAuctionLocations(auctionId);
}
if (!requestDTO.type().equals(AuctionType.ONLINE)) {
locationCommandUseCase.saveAuctionLocations(auctionId, requestDTO.locations());
}
closeAuctionUseCase.delete(auctionId);
closeAuctionUseCase.save(auctionId, findAuction.getAuctionCloseTime());
} catch (OptimisticLockingFailureException e) {
throw new ConflictAuctionUpdateException(CONFLICT_AUCTION_UPDATE);
}
}
@Override
public void delete(final Long userId, final Long auctionId) {
var findAuction = auctionRepositoryPort.findAuctionByIdForUpdate(auctionId);
try {
findAuction.deactivate(userId);
closeAuctionUseCase.delete(auctionId);
} catch (OptimisticLockingFailureException e) {
throw new ConflictAuctionDeleteException(CONFLICT_AUCTION_DELETE);
}
}
}
์ด ๊ฒฐ๊ณผ Auction์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ AuctionCommandService
๋ฟ๋ง ์๋๋ผ UserCommandService
, BidCommandService
์์ ํ์ํ ๋ validateAuctionAccess
๋ฉ์๋๋ฅผ ์ค๋ณต์ ์ผ๋ก ๊ตฌํํ ํ์ ์์ด ๋๋ฉ์ธ์์ ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํด์ก๋ค.
ํญ์ ํ๋ก์ ํธ๋ฅผ ๊ฒฝํํ ๋๋ง๋ค ๊ฐ๊ฒ ๋๋ ํฐ ๊ณ ๋ฏผ์ด ์ฝ๋์ ์ค๋ณต์ฑ์ ์ด๋ป๊ฒ ํ๋ฉด ์ค์ผ ์ ์์๊น์๋ค. ํ์ง๋ง ์ด๋ฒ ๊ธฐํ์ ๋๋ฉ์ธ์ ์บก์ํํด์ ๋ฉ์๋๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ์์ ์ ์ฉํ์ฌ ์ฝ๋์ ์ค๋ณต์ฑ๊ณผ ๋ณต์ก์ฑ์ ์ค์ผ ์ ์์๋ค.
ํ์ง๋ง ์ ๋ฐฉ๋ฒ์ผ๋ก Service ๊ณ์ธต์ ๋ถ๋ด์ ์ค์์ง๋ง, ๋๋ฉ์ธ์ ๋ถ๋ด์ด ๋ฐ๋๋ก ์ปค์ง๊ฒ ๋์๋ค. ์ด ๋ฐฉ๋ฒ์ ๋ํด์๋ ๊พธ์คํ ํ๋ก์ ํธ ํ๋ฉด์ ๊ณ ๋ฏผํ๋ฉฐ ๊ฐ์ ํด ๋๊ฐ์ผ ํ ๋ถ๋ถ์ด๋ค.