Skip to content

Commit

Permalink
Refactor/#17 외부 api 트랜잭션 분리 (#31)
Browse files Browse the repository at this point in the history
* refactor: ParkingApiService에서 읽어오는 부분 Checked Exception 추가

* refactor: ParkingService에서 트랜잭션 담당하도록 변경
  • Loading branch information
This2sho authored Feb 26, 2024
1 parent cda44a0 commit 28047ca
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.parking.application.parking;

import com.example.parking.domain.parking.Parking;
import com.example.parking.domain.parking.ParkingRepository;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class ParkingService {

private final ParkingRepository parkingRepository;

@Transactional
public void saveAll(List<Parking> parkingLots) {
parkingRepository.saveAll(parkingLots);
}

@Transactional(readOnly = true)
public Set<Parking> getParkingLots(Set<String> parkingNames) {
return parkingRepository.findAllByBaseInformationNameIn(parkingNames);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ default boolean offerCurrentParking() {
return false;
}

List<Parking> read();
List<Parking> read() throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public PusanPublicParkingApiService(PusanPublicParkingAdapter adapter,
}

@Override
public List<Parking> read() {
public List<Parking> read() throws Exception {
PusanPublicParkingResponse response = call(1, SIZE);
return adapter.convert(response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public SeoulPublicParkingApiService(SeoulPublicParkingAdapter adapter,
}

@Override
public List<Parking> read() {
public List<Parking> read() throws Exception {
List<SeoulPublicParkingResponse> response = call();
return response.stream()
.map(adapter::convert)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.example.parking.external.scheduler;

import com.example.parking.application.parking.ParkingService;
import com.example.parking.domain.parking.Location;
import com.example.parking.domain.parking.Parking;
import com.example.parking.domain.parking.ParkingRepository;
import com.example.parking.external.coordinate.CoordinateService;
import com.example.parking.external.parkingapi.ParkingApiService;
import java.util.Collection;
Expand All @@ -13,29 +13,22 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.stereotype.Component;

@Slf4j
@Transactional(readOnly = true)
@Service
@RequiredArgsConstructor
@Component
public class ParkingUpdateScheduler {

private final List<ParkingApiService> parkingApiServices;
private final CoordinateService coordinateService;
private final ParkingRepository parkingRepository;
private final ParkingService parkingService;

public ParkingUpdateScheduler(List<ParkingApiService> parkingApiServices, CoordinateService coordinateService,
ParkingRepository parkingRepository) {
this.parkingApiServices = parkingApiServices;
this.coordinateService = coordinateService;
this.parkingRepository = parkingRepository;
}

@Transactional
@Scheduled(fixedRate = 30, timeUnit = TimeUnit.MINUTES)
public void autoUpdateOfferCurrentParking() {
Map<String, Parking> parkingLots = readBy(ParkingApiService::offerCurrentParking);
Expand All @@ -49,17 +42,7 @@ private Map<String, Parking> readBy(Predicate<ParkingApiService> currentParkingA
.filter(currentParkingAvailable)
.map(this::read)
.flatMap(Collection::stream)
.collect(Collectors.toMap(
parking -> parking.getBaseInformation().getName(),
Function.identity(),
(existing, replacement) -> existing
));
}

private Map<String, Parking> findAllByName(Set<String> names) {
return parkingRepository.findAllByBaseInformationNameIn(names)
.stream()
.collect(Collectors.toMap(parking -> parking.getBaseInformation().getName(), Function.identity()));
.collect(toParkingMap());
}

private List<Parking> read(ParkingApiService parkingApiService) {
Expand All @@ -71,6 +54,20 @@ private List<Parking> read(ParkingApiService parkingApiService) {
}
}

private Collector<Parking, ?, Map<String, Parking>> toParkingMap() {
return Collectors.toMap(
parking -> parking.getBaseInformation().getName(),
Function.identity(),
(existing, replacement) -> existing
);
}

private Map<String, Parking> findAllByName(Set<String> names) {
return parkingService.getParkingLots(names)
.stream()
.collect(toParkingMap());
}

private void updateSavedParkingLots(Map<String, Parking> parkingLots, Map<String, Parking> saved) {
for (String parkingName : saved.keySet()) {
Parking origin = saved.get(parkingName);
Expand All @@ -86,7 +83,7 @@ private void saveNewParkingLots(Map<String, Parking> parkingLots, Map<String, Pa
.map(parkingLots::get)
.toList();
updateLocation(newParkingLots);
parkingRepository.saveAll(newParkingLots);
parkingService.saveAll(newParkingLots);
}

private void updateLocation(List<Parking> newParkingLots) {
Expand All @@ -97,7 +94,6 @@ private void updateLocation(List<Parking> newParkingLots) {
}
}

@Transactional
@Scheduled(fixedRate = 30, timeUnit = TimeUnit.DAYS)
public void autoUpdateNotOfferCurrentParking() {
Map<String, Parking> parkingLots = readBy(parkingApiService -> !parkingApiService.offerCurrentParking());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import java.time.LocalDateTime;
Expand All @@ -10,6 +11,7 @@
import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MemberSessionRepositoryTest {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.example.parking.external.scheduler;

import com.example.parking.domain.parking.Parking;
import com.example.parking.external.coordinate.CoordinateService;
import com.example.parking.external.parkingapi.ParkingApiService;
import com.example.parking.fake.BasicParkingRepository;
import com.example.parking.fake.ExceptionParkingApiService;
import com.example.parking.fake.FakeCoordinateService;
import com.example.parking.fake.FakeParkingService;
import com.example.parking.fake.NotOfferCurrentParkingApiService;
import com.example.parking.fake.OfferCurrentParkingApiService;
import java.util.List;
Expand All @@ -15,93 +14,92 @@

class ParkingUpdateSchedulerTest {

private final OfferCurrentParkingApiService offerCurrentParkingApiService = new OfferCurrentParkingApiService(5);
private final NotOfferCurrentParkingApiService notOfferCurrentParkingApiService = new NotOfferCurrentParkingApiService(5);
private final ParkingApiService exceptionParkingApiService = new ExceptionParkingApiService();
private final BasicParkingRepository parkingRepository = new BasicParkingRepository();
private final FakeParkingService parkingService = new FakeParkingService(new BasicParkingRepository());
private final CoordinateService coordinateService = new FakeCoordinateService();


@DisplayName("실시간 주차 대수를 제공하는 API에서 주차장이 0~4까지 저장되어 있는 상태에서 0~9까지 주차장을 읽어와 업데이트한다.")
@Test
void autoUpdateOfferCurrentParking() {
//given
List<Parking> parkingLots = offerCurrentParkingApiService.read();
parkingRepository.saveAll(parkingLots);
OfferCurrentParkingApiService offerCurrentParkingApiService = new OfferCurrentParkingApiService(5);
parkingService.saveAll(offerCurrentParkingApiService.read());
int readSize = 10;
offerCurrentParkingApiService.setReadSize(readSize);

ParkingUpdateScheduler scheduler = new ParkingUpdateScheduler(
List.of(offerCurrentParkingApiService),
coordinateService,
parkingRepository
parkingService
);

//when
scheduler.autoUpdateOfferCurrentParking();

//then
Assertions.assertThat(parkingRepository.count()).isEqualTo(readSize);
Assertions.assertThat(parkingService.count()).isEqualTo(readSize);
}

@DisplayName("실시간 주차 대수를 제공하지 않는 API에서 주차장이 0~4까지 저장되어 있는 상태에서 0~9까지 주차장을 읽어와 업데이트한다.")
@Test
void autoUpdateNotOfferCurrentParking() {
//given
List<Parking> parkingLots = notOfferCurrentParkingApiService.read();
parkingRepository.saveAll(parkingLots);
NotOfferCurrentParkingApiService notOfferCurrentParkingApiService = new NotOfferCurrentParkingApiService(
5);
parkingService.saveAll(notOfferCurrentParkingApiService.read());
int readSize = 10;
notOfferCurrentParkingApiService.setReadSize(readSize);

ParkingUpdateScheduler scheduler = new ParkingUpdateScheduler(
List.of(notOfferCurrentParkingApiService),
coordinateService,
parkingRepository
parkingService
);

//when
scheduler.autoUpdateNotOfferCurrentParking();

//then
Assertions.assertThat(parkingRepository.count()).isEqualTo(readSize);
Assertions.assertThat(parkingService.count()).isEqualTo(readSize);
}

@DisplayName("실시간 주차 대수를 제공하는 API와 제공하지 않는 API는 영향을 안준다.")
@Test
void notAffectBetweenOfferAndNotOfferCurrentParking() {
//given
List<Parking> parkingLots = offerCurrentParkingApiService.read();
parkingRepository.saveAll(parkingLots);
OfferCurrentParkingApiService offerCurrentParkingApiService = new OfferCurrentParkingApiService(5);
NotOfferCurrentParkingApiService notOfferCurrentParkingApiService = new NotOfferCurrentParkingApiService(
5);
parkingService.saveAll(offerCurrentParkingApiService.read());
int readSize = 10;
notOfferCurrentParkingApiService.setReadSize(readSize);

ParkingUpdateScheduler scheduler = new ParkingUpdateScheduler(
List.of(offerCurrentParkingApiService, notOfferCurrentParkingApiService),
coordinateService,
parkingRepository
parkingService
);

//when
scheduler.autoUpdateOfferCurrentParking();

//then
Assertions.assertThat(parkingRepository.count()).isEqualTo(5);
Assertions.assertThat(parkingService.count()).isEqualTo(5);
}

@DisplayName("특정 API에서 예외 발생시, 해당 API는 log를 남기고 무시한다.")
@Test
void autoUpdateWithExceptionApi() {
//given
ParkingUpdateScheduler scheduler = new ParkingUpdateScheduler(
List.of(offerCurrentParkingApiService, exceptionParkingApiService),
List.of(new OfferCurrentParkingApiService(5), new ExceptionParkingApiService()),
coordinateService,
parkingRepository
parkingService
);

//when
scheduler.autoUpdateOfferCurrentParking();

//then
Assertions.assertThat(parkingRepository.count()).isEqualTo(5);
Assertions.assertThat(parkingService.count()).isEqualTo(5);
}
}
17 changes: 17 additions & 0 deletions src/test/java/com/example/parking/fake/FakeParkingService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.parking.fake;

import com.example.parking.application.parking.ParkingService;

public class FakeParkingService extends ParkingService {

private final BasicParkingRepository repository;

public FakeParkingService(BasicParkingRepository repository) {
super(repository);
this.repository = repository;
}

public int count() {
return repository.count();
}
}

0 comments on commit 28047ca

Please sign in to comment.