μ€λμ BoB 10κΈ° 보μμ νκ°λ° νΈλμ 보μ μ루μ μ μ μμ μμ νλ¬λμ μ§νν 1μ°¨ ν νλ‘μ νΈμ λν΄μ μ΄μΌκΈ°ν΄λ³΄λ € νλ€.
ν΄λΉ κΈμ μ΄ 2λΆμμΌλ‘ ν λΉλ©κ³Ό νμ κ³Όμ κ·Έλ¦¬κ³ μλΉμ€ μ€λͺ λ° κ°λ° κ³Όμ μΌλ‘ λλμ΄μ Έ μμ΅λλ€.
- 1μ°¨ ν νλ‘μ νΈ ν λΉλ© λ° νμ μ κ³ λ―Όλ€
- 1μ°¨ ν νλ‘μ νΈ νλ‘ νΈμλλ₯Ό κ°λ°νλ©° νλ κ³ λ―Όλ€
- 1μ°¨ ν νλ‘μ νΈ λ°±μλλ₯Ό κ°λ°νλ©° νλ κ³ λ―Όλ€
- 1μ°¨ ν νλ‘μ νΈ μΈνλΌλ₯Ό κ°λ°νλ©° νλ κ³ λ―Όλ€
νλ‘μ νΈ github λ°λ‘κ°κΈ° -> 보μ μν νΈλν½ λΆμ μ루μ github
μ§λ μκ°μ Front End λ₯Ό 리μ‘νΈλ‘ ꡬμ±νλ©° νλ κΈ°μ μ κ³ λ―Όκ³Ό λ¬Έμ λ€μ λν΄μ μ΄μΌκΈ°λ₯Ό νμλ€.
μ€λμ λ΄κ° κ°μ₯ μ’μνκ³ ν λ΄μμ μ£Όλ‘ λ§‘μλ Backend μ λν΄μ μ΄μΌκΈ°ν΄λ³΄λ € νλ€.
μ°μ Backend λ Spring Boot κ° Framework μΌλ‘ μ¬μ©λμλ€.
μ νν Backend Application μ΄ μ¬κΈ°μ λ¬΄μ¨ μΌμ νλλ©΄?
μ§λ μκ° μ΄μΌκΈ° νμλ μ°λ¦¬ L7 λͺ¨λν°λ§ μ루μ μμλ μλΉμ€ νλ‘μ°κ° λ€μκ³Ό κ°μ΄ νλ¬κ°κ² λλ€.
μλΉμ€ νλ‘μ°
- κ³ κ°μ¬λ κΈ°μ‘΄μ μ΄μμ€μ΄λ μλ²μ ν΄λΉ μλ²κ° μ μ₯νλ access.log κ° μ‘΄μ¬νλ€.
- μ°λ¦¬κ° κ°λ°ν Agent ν¨ν€μ§λ₯Ό μ€μΉνλ€.
- ν΄λΉ Agentμμ access log μ μμΉλ₯Ό μ§μ νκ³ λΆμ μλ²λ‘ access log λ₯Ό μ λ¬νλ€.
- μ΄μ μλ²μ μ±λ₯μ μν΄μ λΆμμ λ€λ₯Έ μλ²μμ μ§ννλ€.
- λΆμ μλ²λ‘ λ€μ΄μ¨ access log λ λΆμμ μ§ννμ¬ κ³΅κ²© μ νμ λ°λΌμ DBμ μ μ₯νλ€.
- ν΄λΉ DB μ κ°λ€μ ν λλ‘ λ³΄μ λ 벨μ μ°μΆνκ³ κ°μ’ μκ°νμ νμν λ°μ΄ν°λ₯Ό μ μ νμ¬ Frontλ‘ λ°ννλ€.
- Front Serverλ Backend μκ² λ°μμ¨ λ°μ΄ν°λ₯Ό κ°μ§κ³ λμ보λμμ μκ°νλ₯Ό μννλ€.
μ¬κΈ°μ Backendκ° κ°μ νλ 6λ²κ³Ό 7λ²μμ νμν μΉ λ°μ΄ν°λ₯Ό DBμμ μ‘°ννκ³ μ¬λ¬ κ°κ³΅μ ν λ€ Front μκ² μλ΅μΌλ‘ λ°νν΄μ£Όλ μν μ μννλ€.
λ°±μλμ κΈ°μ μ κ³ λ―Ό
λ°±μλλ μ¬μ€μ SELECT μΏΌλ¦¬λ§ λ λ¦¬κ³ Get Mapping λ§ λμ΄μλ λ§€μ° μ¬νν μ ν리μΌμ΄μ μΌλ‘ μ€κ³λμλ€.
μλμ SaaS ννλΌλ©΄ μΈμ¦κ³Ό μΈκ°μ λν κ³ λ―Όμ νμμν κ³ , κ·Έμ λ°λΌμ Request Validation μ΄λ μ¬λ¬ μ²λ¦¬λ€μ΄ λ€μ΄κ°μλ― νλ°, μλ κΈ°λ₯μ΄ λ¨μνκΈ° λλ¬Έμ λ³λ€λ₯Έ κ³ λ―Όμ νμ§ μμλ λλ λΆλΆμ΄μλ€.
νμ§λ§ μ΄ κ³Όμ μμλ λλ¦ μ¬λ¬ κ³ λ―Όμ νμκ³ , λλ§μ κ·μΉμ μΈμ°λ €κ³ λ Έλ ₯νλ©° λ§λ¬λ κ²°κ³Όλ¬Όκ³Ό λ¬Έμ λ€μ 곡μ ν΄λ³΄λ € νλ€.
μ£Όμ λ ν¬κ² 2κ°μ§ μ΄λ€.
- ꡬν μν€ν μ²
- ν μ€νΈ μ λ΅
ꡬν μν€ν μ³
3-Tier ꡬ쑰μμ νλ‘μ νΈλ₯Ό ν λλ κ±°μ Layered Architecture λ₯Ό Spring Boot μμ μ¬μ©νκ³€ νλ€.
μ¬μ€μ Spring Boot μμ μ¬μ©ν μν€ν μ² ν¨ν΄μ λ΄κ° λ§μ΄ λͺ¨λ₯΄λ λΆλΆλ μκ³ ν΄μ κΈ°μ‘΄μ λ§μ΄ ν΄μλ λ μ΄μ΄λ μν€ν μ²λ‘ API μλ²λ₯Ό ꡬμ±νλ€.
κ°λ¨νκ² Layeted Architecture λ OSI 7 Layer μ κ°μ΄ κ°κ°μ λ μ΄μ΄κ° νλ μΌμ΄ λͺ ννκ³ νλμ λ μ΄μ΄μμ λλΈ λ°μ΄ν°λ₯Ό λ€μ λ μ΄μ΄λ‘ λ겨주λ λ°©μμ μ·¨νλ€
νλ‘μ νΈ κ΅¬μ‘°λ λ€μκ³Ό κ°λ€
Persistence Layer : Domain ν¨ν€μ§
Domain ν¨ν€μ§μμλ Data μ μμν (Persistence) μ κ΄ν μ²λ¦¬λ₯Ό νλ λΆλΆμ΄λ€.
λλ Domain μμμ ν¬κ² 3ννΈλ‘ λλ΄λ€.
- Jpa Entity
- JpaRepository λ₯Ό μμλ°λ μΈν°νμ΄μ€
- dto
Entity
Entity λ Jpa λ₯Ό 곡λΆν΄λ΄€λ μ¬λλ€μκ²λ μΉκ·Όν κ°μ²΄μ΄λ€.
Jpaμ ν΅μ¬μΈ persistence context μ λ€μ΄κ° λμ κ°μ²΄λ‘ DB ν μ΄λΈμ λμλλ νλμ ν΄λμ€λ‘ μκ°νλ©΄ μ¬μ΄λ°, DB ν μ΄λΈμ Object ν νλ ORMμ νΉμ±μμ κ°μ₯ κΈ°λ³Έμ΄ λλ μΈμ€ν΄μ€λ€μ μλ―Ένλ€.
JpaRepository λ₯Ό μμλ°λ μΈν°νμ΄μ€
Spring Data Jpa μμλ κ°λ¨ν CRUD κΈ°λ₯μ μ²λ¦¬νκΈ° μν΄μ κΈ°λ³Έμ μΌλ‘ JpaRepository λ₯Ό μ 곡νλ€.
ν΄λΉ μΈν°νμ΄μ€λ₯Ό μμλ°κ³ λλ 쿼리 λ©μλμ λ©μλ λ€μμ ν΅ν 쿼리 μμ±μ μ΄μ©νμλ€.
public interface TotalRepository extends JpaRepository<Total, Long> {
long countByTimestampBetween(LocalDateTime from, LocalDateTime to);
List<Total> findTop100ByTimestampBetween(LocalDateTime minusDays, LocalDateTime now);
}
DTO
DTO λ Martin Fowler κ° μ μν λ°μ΄ν° μ μ‘λ§μ μν κ°μ²΄λ₯Ό μλ―Ένλ€.
μ΄ κΈμ μ°λ μμ μ μκ² λμμ§λ§ DTO λ ν¨μκ° μ‘΄μ¬νμ§ μλλ°, λ΄ μ½λμμλ DTOμ λ‘μ§μ΄ μ‘΄μ¬νλ€ γ γ ..
μμ νλκ±Έλ‘..
Business Layer
Buisiness Layer μμλ Presenter Layer μ Persistence Layer μ¬μ΄μ μμΉνμ¬ μ°λ¦¬μ Business Logic μ ꡬμ±νκ³ μ μνλ λ μ΄μ΄λ€.
μ€μ Databaseμ μνΈμμ©μ Business layer μμ μ΄λ€μ§κΈ° λλ¬Έμ Transaction Boundary λ₯Ό μ€μ ν΄μ£Όμλ€.
μ€νλ§ μ½λμμλ Presentation Layer μμ λ°μ DTOλ₯Ό κ°μ§κ³ Entity λ‘ λ³ννλ©° λΉμ¦λμ€ λ‘μ§μ΄ νΈμΆλ μ΄ν λ€μ DTOλ‘ λ³ννλ κ³Όμ μ κ±°μΉλ€.
λν μ΄ λΆλΆμμ jdoc μ μμ±ν΄μ κ°λ μ±μ λμλ€.
/**
* νμ¬λ₯Ό κΈ°μ€μΌλ‘ 24μκ° μ΄λ΄μ λͺ¨λ λΉμ μ μμ²μ κ°μλ₯Ό μΆλ ₯νλ€.
*
* @param trafficType νΈλν½ μ ν
* @return νμ¬ μκ°μΌλ‘λΆν° -24 μκ°μ λͺ¨λ λΉμ μ λ‘κ·Έμ κ°μλ₯Ό λ°ννλ€.
*/
public TotalTrafficResponse getTodayTrafficSummaries(TrafficType trafficType) {
LocalDateTime from = LocalDateTime.now().minusDays(1L);
long count = 0;
if(trafficType.equals(TrafficType.ALL)) {
count = totalRepository.countByTimestampBetween(from, LocalDateTime.now());
} else if (trafficType.equals(TrafficType.THREAT)) {
count = abnormalRepository.countByTimestampBetween(from, LocalDateTime.now());
}
return TotalTrafficResponse.builder()
.id(1L)
.count(count)
.timestamp(from)
.build();
}
μμ μ½λλ μ€λ λ°μν μ΄ νΈλν½μ λ°ννλ€.
Presentation Layer
Presentataion Layer λ 2-tier μν€ν μ³μμ HTML νμ΄μ§λ₯Ό λ°ννκΈ°λ νλ€.
μ°λ¦¬λ 3-tier μ μν€ν μ²λ‘ JSON μ μ΄μ©ν API Call μ λ°©ν₯μΌλ‘ μ‘μκΈ° λλ¬Έμ JSON μ λ°ννλλ‘ νλ€.
@RestController
@RequestMapping(value = "/api/traffics", produces = "application/json")
public class TrafficController {
private final TrafficService trafficService;
public TrafficController(TrafficService trafficService) {
this.trafficService = trafficService;
}
@GetMapping("/summaries")
public ResponseEntity<TotalSummariesResponse> getTodayTrafficsSummaries() {
return ResponseEntity.ok().body(trafficService.getTodaySummaries());
}
}
μ€μ μ½λμμμλ μμ κ°μ΄ Business Layer μ μ‘΄μ¬νλ Service
μ½λλ₯Ό μμ±μ μ£Όμ
μ ν΅ν΄μ λΉμ μμ±νκ³ , API Endpoint μμ λ°λ‘ νΈμΆνμ¬ μ°κ²°ν΄μ£Όμλ€.
μ΄ κ³Όμ μμ μ μ μλ μ¬μ€μ Service.java μμλ DTOλ₯Ό 맀κ°λ³μλ‘ λ°κ³ μ μ ν λΉμ¦λμ€λ₯Ό μ²λ¦¬ν λ€, DTOλ‘ λ°ννλ€λ κ²μ΄λ€
μ΄ κ³Όμ μ νλμ κ·Έλ¦ΌμΌλ‘ νννμλ©΄ λ€μκ³Ό κ°μ΄ λμ¬ κ²μ΄λ€
μ΄λ° κ³Όμ μΌλ‘ λλ μ£Όλ‘ κ°λ°μ ν΄ μμκ³ , ν΄λΉ νλ‘μ νΈμμλ μ μ©μ νμλ€.
μ΄ κ³Όμ μμ μλλΌλ©΄ Service μ½λκ° λ€μ΄κ°λ Business Layer μμ Validation μ΄ νμμ μΌλ‘ μ΄λ£¨μ΄μ ΈμΌ νλ€.
μλ₯Ό λ€μ΄ μ λ ₯ κ°, Requets Body μ Data κ° μ μ ν λ°μ΄ν°μΈμ§λ₯Ό νμΈνλ κ³Όμ μ΄ νμνμ§λ§ λͺ¨λ GET μμ²μΌλ‘ μ²λ¦¬λ₯Ό νλ API μλ²μμ μ΄λ λΆνμν κ³Όμ μ΄λΌ μκ°ν΄μ μλ΅μ νμλ€.
ν μ€νΈ μ λ΅
λ΄κ° κ°μ₯ μ’μνλ λΆλΆμ΄ λ°λ‘ Test Code μ΄λ€.
ν μ€νΈ μ½λλ₯Ό μμ±νκ³ μ€ν¨νλ ν μ€νΈλ₯Ό λ¨Όμ ꡬμ±ν λ€, μ±κ³΅νκΈ° κΉμ§ μ¬λ¬ μμ μ ν΄μ£Όμ΄ ν μ€νΈκ° ν΅κ³Όνλ©΄ κ·Έ λ§ν μ§λ¦Ών¨μ μ΄λμλ λλ μ μλ€
νν λ§ νλ TDD λ₯Ό λλ μ’μνλλ°, λ΄ λλ¦ κ·μΉμ΄ μ‘΄μ¬νλ€.
λͺ¨λ μ½λλ₯Ό TDDλ‘ κ°λ°μ νλ€λ©΄ μ’κ² μ§λ§ νμ€μ μΌλ‘ κ·Έκ² μ½μ§λ§μ μλ€.
μ λ§ λͺ ννκ³ κ°λ¨ν μ½λ, μλ₯Ό λ€μ΄ Entity μ CRUD ν μ€νΈλΌλμ§? μ΄λ° μ½λλ κ³Όκ°ν TDDλ₯Ό νμ§ μλλ€.
κ·Έλ₯ μ μ λ‘μ§μ λ¨Όμ μ§μ§λ§, μ΄λ €μ΄ κ°λ μ ꡬνν΄μΌ νλ κ²½μ°λ 무쑰건 TDDλ₯Ό μ΄μ©νλ€.
λλ μ΄ κ³Όμ μμ λ§λ¬λ λ¬Έμ λ€κ³Ό κ³ λ―Όλ€μ 곡μ ν΄λ³΄λ € νλ€.
Behavior Driven Development
BDDλΌκ³ λΆλ¦¬λ μ΄κ²μ TDDλ₯Ό κ·Όκ°μΌλ‘ νλ TDDμ λλ€λ₯Έ ꡬνλ²?μ΄λ€
BDDμ λν μμΈν μ¬νμ ν΄λΉ λΈλ‘κ·Έ JUnit5 BDDMockitoλ‘ μμ보λ TDDμ BDD μ°¨μ΄μ λ° μ€μ΅ μμ νμΈν μ μμ΅λλ€!
BDDλ ν¬κ² TDDμ λ€λ₯΄μ§ μλ€.
BDDλ Red -> Green -> Refactor μ¬μ΄ν΄μ λλ κ²μ λμΌνκ³ ν μ€νΈ μ½λλ₯Ό μ§λ λ°©λ²μ΄ μ’ λ 체κ³ν λμ΄μλ€κ³ μκ°νλ©΄ λλ€.
μ°λ¦¬κ° μΌλ°μ μΌλ‘ TDDλ₯Ό λ°°μΈ λ, λ€μκ³Ό κ°μ΄ λ°°μ΄λ€.
given-when-then
μ΄ κ²μ΄ λ°λ‘ BDDμ ν΅μ¬μ΄λ€.
μΈκ°μ΄ μ½κΈ° μ¬μ΄ λ°©λ²μΌλ‘ ν μ€νΈ μ½λλ₯Ό λλ λλ κ²μ μλ―Ένλλ°, λλ μ΄λ₯Ό μν΄ JUnit5 μ BDDMockito ν΄λμ€λ₯Ό μ΄μ©νλ€.
class TrafficServiceTest {
private TrafficService trafficService;
private final TotalRepository totalRepository = mock(TotalRepository.class);
private final AbnormalRepository abnormalRepository = mock(AbnormalRepository.class);
@BeforeEach
void init() {
trafficService = new TrafficService(totalRepository, abnormalRepository);
given(totalRepository.countByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
.willReturn(12327L);
}
@ParameterizedTest
@DisplayName("μκ° νμ
(5λΆ) κ° μ£Όμ΄μ§λ©΄ 8 κ°μ dto λ°ν - μ±κ³΅")
@MethodSource("paramsForGetTrafficCountByPeriodWithValid")
void getTrafficBetween_valid(PeriodType periodType) {
List<TotalTrafficResponse> response = trafficService
.getTrafficCountByPeriod(periodType);
TotalTrafficResponse firstTotalResponseData = response.get(0);
assertAll(
() -> assertEquals(8, response.size()),
() -> assertEquals(1, firstTotalResponseData.getId()),
() -> assertEquals(12327L, firstTotalResponseData.getCount()),
() -> assertNotNull(firstTotalResponseData.getTimestamp())
);
}
}
μμ μ½λλ₯Ό νμΈν΄λ³΄μ.
Unit ν μ€νΈλ₯Ό μν΄μ ν μ€νΈ λμ ν΄λμ€λ₯Ό κ³ λ¦½νμλ€.
μλ²½ν κ³ λ¦½μ μν΄μ λμ ν΄λμ€λ₯Ό μ μΈν λͺ¨λ ν΄λμ€λ₯Ό κ°μ§λ‘ λ§λ€μ΄μ£Όλ κ³Όμ μ΄ νμνλ€.
λλ mock()
μ μ΄μ©ν΄μ νλΌλ―Έν°λ‘ λ€μ΄κ°λ ν΄λμ€κ° κ°μ§λ‘ μ΄λ€ μΌμ μνν μ§ κ²°μ ν΄μ£Όμλ€.
init()
λ©μλμμλ ν
μ€νΈ λμ ν΄λμ€κ° νλμ μΌμ μνν λ, νμν μμ‘΄ κ΄κ³μ λν΄μ μ£Όμ
μμΌμ€λ€.
μ΄ κ³Όμ μμ given()
μ΄λΌλ BDDMockito μ λ©μλλ₯Ό μ¬μ©νλ€.
κ·Έλ¦¬κ³ ν μ€νΈ λμ ν΄λμ€μ λ©μλλ₯Ό νΈμΆνμ¬ κ²°κ³Όλ₯Ό μμΈ‘ν μ μλλ‘ νμ¬ then μ μμ±νλ€.
ν μ€νΈ μ½λμ Parameter μ λ¬νκΈ°
ν μ€νΈ μ½λλ₯Ό μμ±νλ€λ³΄λ©΄ μ€λ³΅λ ν μ€νΈ μ½λκ° λ§μ΄ λ°μνκ² λλ€.
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄μ λλ JUnit5 μ μ‘΄μ¬νλ ParameterizedTest λ₯Ό μ΄μ©νμλ€.
ν μ€νΈ μ½λμ νλΌλ―Έν°λ₯Ό λ£λ λ°©λ²μ λν΄μλ ν΄λΉ λΈλ‘κ·Έμ @ParameterizeTest λ‘ νλμ ν μ€νΈ μ½λμμ μ¬λ¬ κ°μ νλΌλ―Έν° ν μ€νΈνκΈ° μμ νμΈν μ μμ΅λλ€!
λλ μ΄λ₯Ό Enum νμ μ 맀κ°λ³μλ‘ λ°λ μλΉμ€ λ‘μ§μ λν ν μ€νΈλ₯Ό ν λ μ¬μ©νλ€.
νλ‘μ νΈμμ μ€μκ° νΈλν½μ νΉμ μ£ΌκΈ°μ λ§κ² λ°νν΄μΌ νλ μ½λκ° μλλ°, μ΄ μν©μμ Test Codeλ₯Ό μ€ννλ Methodμ 맀κ°λ³μλ‘ λ°μμ μ²λ¦¬νλ€.
class TrafficServiceTest {
private TrafficService trafficService;
private final TotalRepository totalRepository = mock(TotalRepository.class);
private final AbnormalRepository abnormalRepository = mock(AbnormalRepository.class);
@BeforeEach
void init() {
trafficService = new TrafficService(totalRepository, abnormalRepository);
given(totalRepository.countByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
.willReturn(12327L);
given(abnormalRepository.countByTimestampBetween(any(LocalDateTime.class), any(LocalDateTime.class)))
.willReturn(417L);
}
@ParameterizedTest
@DisplayName("μκ° νμ
(5λΆ) κ° μ£Όμ΄μ§λ©΄ 8 κ°μ dto λ°ν - μ±κ³΅")
@MethodSource("paramsForGetTrafficCountByPeriodWithValid")
void getTrafficBetween_valid(PeriodType periodType) {
List<TotalTrafficResponse> response = trafficService
.getTrafficCountByPeriod(periodType);
TotalTrafficResponse firstTotalResponseData = response.get(0);
assertAll(
() -> assertEquals(8, response.size()),
() -> assertEquals(1, firstTotalResponseData.getId()),
() -> assertEquals(12327L, firstTotalResponseData.getCount()),
() -> assertNotNull(firstTotalResponseData.getTimestamp())
);
}
private static Stream<Arguments> paramsForGetTrafficCountByPeriodWithValid() {
return Stream.of(
Arguments.of(PeriodType.WEEK),
Arguments.of(PeriodType.DAY),
Arguments.of(PeriodType.FIVE_MINUTE)
);
}
}
paramasForGetTrafficCountByPeriodWithValid
λ©μλμμ κ°κ° μ£ΌκΈ°μ λ§λ Enum νμ
μ λ°ννκ³ ,
getTrafficBetween_valid
μμ ν΄λΉ νλΌλ―Έν°λ₯Ό λ°λλ‘ μ½λλ₯Ό μμ±ν΄μ 3κ°μ λμΌν ν
μ€νΈ μ½λλ₯Ό νλλ‘ μ€μ΄λλ‘ νλ€.
μ΄λ° λ°©μμΌλ‘ μ‘°κΈμ κ°λ μ±μ μ±κ²Όκ³ , λ΄κ° μμ£Ό μ μ©νλ λ°©λ²μ΄κΈ°λ νλ μ΄λ° κ³ λ―Όμ΄ μλ μ¬λλ€μκ²λ κΌ μΆμ²νλ λ°©λ²μ΄λ€.
Coverage
λ΄κ° ꡬνν΄μΌν API λͺ μΈλ λ€μκ³Ό κ°μλ€.
APIλ€μ ꡬνν λ, λλ§μ μ½μμ΄ μλλ° κ·Έκ²μ λ°λ‘ 컀λ²λ¦¬μ§λ₯Ό μΌμ μμ€ μ΄μμΌλ‘ κΌ λ§μ‘±μν€λ κ²μ΄λ€.
λλ κΈ°μ€μ νΉλ³ν κ²½μ°κ° μλλΌλ©΄ μ½λ 컀λ²λ¦¬μ§ 90% μ΄μμΌλ‘ μ‘λλ€.
100%λ ν μ μλ€λ©΄ μ’κ² μ§λ§ μ½κ°μ Tradeoffκ° μλλ― νλ€.
μλ₯Ό λ€λ©΄ 둬볡μ κ΄ν μ½λλ, DTOμ κ΄ν λͺ¨λ ν μ€νΈλ₯Ό λ§μ‘±μν€λ©΄ μ’κ² μ§λ§, μμ°μ±μ 보μλ©΄ 90%κ° λμκ²λ μ ν©νκ³ , λ§μ‘±μ€λ¬μ΄ μμ€μ΄μλ€.
Backend λ₯Ό κ°λ°νλ©° λλ νκΈ°μ κ°μ
μ΄λ²μ λ΄κ° 맑μ APIλ μ¬μ€ ν¬κ² μ΄λ €μ΄ λΆλΆμ΄ μ‘΄μ¬νμ§ μμλ€.
λ¬Έμ κ° μλ€λ©΄ λ΄κ° λ무 λ§μ λΆλΆμ 맑μλ κ²... μ΄λΌκ³ νκ³ μΆλ€.
Front Back, κ·Έλ¦¬κ³ μΈνλΌμ λ°°ν¬κΉμ§ λ΄κ° 맑μλ²λ €μ λ λ©μ§κ³ μ°μν μ½λλ₯Ό μ§λλ°μ μ κ²½μ λ§μ΄ μ°μ§ λͺ»νλ€.
κ·Έλλ λ΄κ° μ ν΄λμ λλ¦μ μμ€μ λ§μ‘±νλ νλ‘μ νΈμ§λ§ μ΄ νλ‘μ νΈμμ μμ¬μ΄ λΆλΆμ κΌ½μλ©΄ 2κ° μ λλ‘ λ§ν μ μλ€.
- active profile λ₯Ό μ¬μ©νμ§ λͺ»ν κ²
- Between μ°μ°μ κ°μ νμ§ λͺ»ν κ²
active profile λ₯Ό μ¬μ©νμ§ λͺ»ν κ²
ν μ€νΈ μ½λλ₯Ό μμ±ν λ, λλ H2 DBλ₯Ό μ¬μ©ν΄μ λ©λͺ¨λ¦¬μμ λμκ°κ² ν΄μ ν μ€νΈλ₯Ό νκ³ μ€μ Production νκ²½μμλ Mysql μ΄ κ΅¬μ±λμ΄ μμλ€.
μ€λμ μν΄μ Spring Boot μ Active Profiles μ λν΄μ 곡λΆλ₯Ό νμλλ°, μκ°μ΄ λ무 μ΄λ°ν λλ¨Έμ§ DB μ€μ νλ μ€μ νμΌμμ λ¨μ§ μ£ΌμμΌλ‘ Test νκ²½κ³Ό Production νκ²½μ λλ΄λ€..
spring:
# H2 μ½μ μ€μ
h2:
console:
path: /h2-console
enabled: true
settings:
web-allow-others: true
# DB μ€μ
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:l7-monitor
username: l7
password:
# jpa μ€μ
jpa:
hibernate:
ddl-auto: create-drop
# datasource:
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/bob_l7?characterEncoding=utf8
# username: root
# password: 1111
κ·Έλ¬λ€λ³΄λ λΉλνλ κ³Όμ μμ Production νκ²½μμλ ν
μ€νΈ μ½λκ° κΌ¬μ¬μ λΉλκ° λμ§ μμκ³ κ²°κ΅ gradle bootJar
λ‘ λΉλλ₯Ό ν΄λ²λ Έλ€..
Spring bootμ profiles μ λν΄μλ ν΄λΉ λΈλ‘κ·Έμ Profile.active μ μ΄μ©νμ¬ λ€λ₯Έ μ€μ μ 보(application.properties) λΆλ¬μ€κΈ°
λ§μ½ λ€μμ μ¬μ κ° λλ€λ©΄ κΌ Active Profiles λ₯Ό μ¬μ©νλ € νλ€.
Between μ°μ°μ κ°μ νμ§ λͺ»ν κ²
Spring Data JPA μμλ λ€μν 쿼리 λ©μλκΈ°λ₯μ μ§μνλλ°, κ·Έ μ€ νλκ° λ°λ‘ λ©μλ μ΄λ¦μΌλ‘ 쿼리λ₯Ό λ§λλ κΈ°λ₯μ΄ μλ€.
λ΄ API μμ μ€λμ νΈλν½λμ μκ°λ λ³λ‘ μλ €μ£Όλ μ½λκ° μμλ€.
ν΄λΉ μ½λμμλ long countByTimestampBetween(LocalDateTime from, LocalDateTime to);
μ΄λΌλ 쿼리 λ©μλλ₯Ό νΈμΆνλλ°, 보λ€μΆμ΄ νλμ μκ°λλ§ μ‘°νλ₯Ό ν μ μκΈ° λλ¬Έμ 8κ°κ° νμνλ€λ©΄ μ΄ 8λ²μ νΈμΆνκ² λλ€.
μ΄λ₯Ό NameQuery λ₯Ό μ΄μ©νκ±°λ λ€λ₯Έ λ°©λ²μ μ΄μ©νλ€λ©΄ νλμ 쿼리μμλ ν μ μμ§λ§ νμ§ μμ μμ¬μμ΄ λ¨λλ€.
λκΈ