๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

25-03-09

๐Ÿ“Œ Daily Reportโ€‹

https://github.com/ssginc-be/DOKI/issues/52


๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์ƒํ™ฉโ€‹

์—ฌ์ „ํžˆ ๊ตฌํ˜„ํ•  ๊ฒƒ์ด ์‚ฐ๋”๋ฏธ์ด๊ณ , ์ฝ”๋“œ ๋ฆฌ๋ทฐ๊ฐ€ ๋“ค์–ด์™€์„œ ๊ฒ€ํ†  ํ›„ ๋ฆฌํŒฉํ† ๋งํ•ด์•ผ ํ•˜๊ณ , Kibana ์ธ์ฝ”๋”ฉ ๋ฌธ์ œ๋„ ํ•ด๊ฒฐ ๋ชปํ–ˆ๋‹ค.

task_๋ชฉ๋ก

ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹ใ…‹

๋†€๋ž๊ฒŒ๋„ ์˜ค๋Š˜ ํ•˜๊ณ ์‹ถ์€ ๋งŒํผ ์ ์–ด๋†“์€ ๊ฒƒ์ด๊ณ , ์‹ค์ œ๋กœ๋Š” ํ•  ์ผ ๋” ๋งŽ์Œ.

API Gateway์™€ https ์„ค์ •์„ ๋ง‰ํŒ์— ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉด์„œ ํ•˜๋ ค ํ–ˆ๋Š”๋ฐ... ๋ชฉ์š”์ผ๋ถ€ํ„ฐ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ๋ณด๊ณ ์„œ ์ž‘์„ฑํ•˜๋ ค๋ฉด ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๊ฒ€ํ† ๊นŒ์ง€๋งŒ ํ•˜๊ณ  ๋ฆฌํŒฉํ† ๋ง์„ ํ›„์ˆœ์œ„๋กœ ๋ฐ€์–ด์•ผ ํ•  ๋“ฏ ์‹ถ๋‹ค.๐Ÿ™„


๐Ÿ“Œ JS ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์Šค์ฝ”ํ”„โ€‹

๊ทธ๋™์•ˆ ์™œ async-await ๋ถ™์—ฌ๋„ ๋ ๋•Œ๊ฐ€ ์žˆ๊ณ  ์•ˆ๋ ๋•Œ๊ฐ€ ์žˆ๋Š”์ง€ ์˜๋ฌธ์ธ ์ ์ด ๋ช‡ ๋ฒˆ ์žˆ์—ˆ๋Š”๋ฐ,

์ด ๋ ˆํผ๋Ÿฐ์Šค์—์„œ ์ค‘์š”ํ•œ ๋ช…์–ธ์„ ํ•˜๋‚˜ ๋‚จ๊ฒจ๋†“์€ ๊ฒƒ ๊ฐ™๋‹ค.

"์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ async/await ์‚ฌ์šฉ ์‹œ ๋™์ผํ•œ ์Šค์ฝ”ํ”„์—์„œ awaitํ•˜์ง€ ์•Š์œผ๋ฉด ์•ž์„œ ์‹คํ–‰ํ•œ async๋ฅผ ๊ธฐ๋‹ค๋ ค์ฃผ์ง€ ์•Š๋Š”๋‹ค."

๊ทธ๋Ÿฌํ•˜๋‹ค. ์Šค์ฝ”ํ”„...

์™œ ์•„๋ฌด๋„ ์Šค์ฝ”ํ”„์— ๋Œ€ํ•ด์„œ ์–˜๊ธฐ๋ฅผ ์•ˆํ–ˆ๋˜๊ฑฐ์ง€? ์ด ์„ธ ๊ธ€์ž๋กœ ์ดํ•ด๊ฐ€ ๋ฐ”๋กœ ๋˜๋Š”๋ฐ.


๐Ÿ“Œ ERD 4์ฐจ ์ˆ˜์ •์— ๋”ฐ๋ฅธ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธโ€‹

์‚ฌ๊ฑด์˜ ๋ฐœ๋‹จ์€ ์ด๋Ÿฌํ–ˆ๋‹ค.

  1. ํŒ์—…์Šคํ† ์–ด ์ƒ์„ธ ํŽ˜์ด์ง€์— ์šด์˜ ์‹œ์ž‘ ์‹œ๊ฐ„๊ณผ ์ข…๋ฃŒ ์‹œ๊ฐ„์ด ๋“ค์–ด๊ฐˆ ํ•„์š”๊ฐ€ ์žˆ์Œ์„ ๋Š๋‚Œ
  2. ์–ด ๊ทผ๋ฐ ํŒ์—…์Šคํ† ์–ด ํ…Œ์ด๋ธ”์— ๊ด€๋ จ ์นผ๋Ÿผ์ด ์—†๋„ค? ์ถ”๊ฐ€ํ•ด์•ผ์ง€
  3. ์ด๋ฏธ ์šด์˜ ์‹œ์ž‘์ผ์„ storeStart, ์ข…๋ฃŒ์ผ์„ storeEnd ๋„ค์ด๋ฐ์œผ๋กœ ๋‘๊ณ  ์žˆ์Œ
  4. ์šด์˜ ์‹œ๊ฐ„์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ์šด์˜ ์ผ์ž ์นผ๋Ÿผ๋ช…์— Date๋ฅผ ๋ถ™์ด๊ณ , ์šด์˜ ์‹œ๊ฐ„์€ ๋’ค์— Time์„ ๋ถ™์ด๊ธฐ๋กœ ํ•จ
  5. Store ์—”ํ‹ฐํ‹ฐ ์ˆ˜์ •

it_was_a_complete_disaster

๊ทธ๋ฆฌ๊ณ  ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ํฌ๊ฒŒ ๋งž์•˜๋‹ค. ^0^

data.sql ํŒŒ์ผ ์–˜๊ฐ€ ์ œ์ผ ํฐ ๋ฌธ์ œ์ธ๋ฐ, ์ด๋ ‡๊ฒŒ ์นผ๋Ÿผ ๋ณ€๊ฒฝ ๋“ฑ์œผ๋กœ ํ…Œ์ด๋ธ” ์ŠคํŽ™์ด ๋ฐ”๋€Œ์–ด์„œ ํŒŒ์ผ ๋‚ด์˜ INSERT๊ฐ€ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•„๋„, ์•„๋ฌด๋Ÿฐ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋ž˜์„œ ๋šœ๊นŒ ๋งž์œผ๋ฉด์„œ ์•Œ๊ฒŒ ๋˜๋Š” ๋ถ€๋ถ„์ธ๋ฐ ์ด๋ฏธ ์ „์— ๋งž์•„๋ด์„œ, ๋‹คํ–‰ํžˆ ๊ธˆ๋ฐฉ ๋ˆˆ์น˜์ฑ„๊ธด ํ–ˆ๋‹ค.

mock data ์ฟผ๋ฆฌ๊ฐ€ ์•ˆ๋‚ ๋ผ๊ฐ€๋‹ˆ๊นŒ ES ์ธ๋ฑ์‹ฑ์ด ์•ˆ๋Œ์•„๊ฐ€๊ณ  -> ์ธ๋ฑ์‹ฑ์ด ์•ˆ๋˜๋‹ˆ๊นŒ ๋ฉ”์ธํ™”๋ฉด์—์„œ ๋ชฉ๋ก ์กฐํšŒ๊ฐ€ ์•ˆ๋˜๋”๋ผ.

INSERT ์ฟผ๋ฆฌ๋ฅผ ๋ฐ”๋€ ํ…Œ์ด๋ธ” ์ŠคํŽ™์— ๋งž์ถฐ ์ˆ˜์ •ํ•˜๋‹ˆ ๋‹ค์‹œ ์ธ๋ฑ์‹ฑ์€ ๋Œ์•„๊ฐ€๋Š”๋ฐ, ๋‹น์—ฐํžˆ ES Document ์ŠคํŽ™๋„ ๊ฐ™์ด ์ˆ˜์ •ํ–ˆ์–ด์•ผ ํ–ˆ๋‹ค. ์ด๋ฒˆ์— ์ถ”๊ฐ€๋œ ์นผ๋Ÿผ์ด ์ธ๋ฑ์‹ฑ ๋Œ€์ƒ์€ ์•„๋‹ˆ์—ˆ์ง€๋งŒ, ์ธ๋ฑ์‹ฑํ•˜๋Š” ์šด์˜ ์ผ์ž ์นผ๋Ÿผ๋ช…์ด ๋ฐ”๋€Œ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ ‡๊ฒŒ ์ˆ˜์ •ํ•ด์„œ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ–ˆ๋Š”๋ฐ, ํƒ€์ž„๋ฆฌํ”„๊ฐ€ ํ„ฐ์ง€๋”๋ผ. ์ตœ์ดˆ ๋ Œ๋”๋ง๋งŒ ํ…œํ”Œ๋ฆฟ ์—”์ง„์œผ๋กœ ํ•˜๊ณ  ์ดํ›„์˜ ๋ Œ๋”๋ง ๋ณ€ํ™”๋Š” axios ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ ค ํ–ˆ๋Š”๋ฐ ์ด ๋ฐฉ์‹์ด... ๋ณ„๋กœ์ธ๊ฐ€?

์•„๋ฌดํŠผ ์นผ๋Ÿผ๋ช… ๋ณ€๊ฒฝ์œผ๋กœ flatpickr ์„ค์ •๊นŒ์ง€ ํ„ฐ์ ธ์„œ view๋‹จ๊นŒ์ง€ ์‹น ๊ณ ์ณ์ค˜์•ผ ํ–ˆ์Œใ…œ


๐Ÿ“Œ @DynamicInsert๋ฅผ ๋“œ๋””์–ด ์ดํ•ดํ•จโ€‹

ReservationEntry.java
@Entity
@EntityListeners(AuditingEntityListener.class)
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReservationEntry {
// ...

@Column(nullable = false)
@ColumnDefault("0")
private Integer reservedCount; // ์˜ˆ์•ฝ์ž ์ˆ˜

// ...
}
StoreService.java
public class StoreService {
// ...

// 5. ReservationEntry ์ƒ์„ฑ
List<ReservationEntry> reList = new ArrayList<>();
LocalDate endDate = dto.getStoreEndDate();
LocalTime endTime = dto.getStoreEndTime();

int reserveGap = dto.getReserveGap();
int capacity = dto.getCapacity();
for (LocalDate curDate = dto.getStoreStartDate(); !curDate.isAfter(endDate); curDate = curDate.plusDays(1)) {
for (LocalTime curTime = dto.getStoreStartTime(); curTime.isBefore(endTime); curTime = curTime.plusMinutes(reserveGap)) {
reList.add(ReservationEntry.builder()
.store(store)
.entryDate(curDate)
.entryTime(curTime)
.capacity(capacity)
.entryStatus(ReservationEntry.EntryStatus.OPEN)
.build()
);
}
}
reRepo.saveAll(reList);

// ...
}
20:14:26.751[http-nio-9093-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Failed to complete request: org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value: com.ssginc.commonservice.reserve.model.ReservationEntry.reservedCount

์™œ @ColumnDefault("0")๊ฐ€ ์•ˆ๋จนํžˆ๊ณ  ์—๋Ÿฌ๊ฐ€ ๋œจ๋Š”์ง€ ์˜๋ฌธ์ด์—ˆ๋Š”๋ฐ, ์ด ๊ธ€์„ ์ฐธ๊ณ ํ•ด๋ณด๋ฉด @DynamicInsert๋ฅผ ์—”ํ‹ฐํ‹ฐ์— ๋ช…์‹œํ•˜์ง€ ์•Š์•˜๊ธฐ์—

INSERT ์ฟผ๋ฆฌ์—์„œ reserveCount๋ฅผ null๋กœ ๋„ฃ์œผ๋ ค ํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

ํ•˜์ง€๋งŒ ํ”„๋กœ์ ํŠธ์˜ ๊ฒฝ์šฐ MySQL์—์„œ ๋””ํดํŠธ ๊ฐ’ 0์„ ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— @DynamicInsert๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์•„์˜ˆ INSERT ์ฟผ๋ฆฌ์—์„œ reserveCount ์นผ๋Ÿผ์ด ์ œ์™ธ๋˜์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด ๋งž๋‹ค.

...๊ทธ๋Ÿฐ๋ฐ @DynamicInsert ๋„ฃ์–ด๋„ ๋˜‘๊ฐ™์ด ์˜ค๋ฅ˜ ๋ฐœ์ƒํ•ด์„œ ์ผ๋‹จ ๋•œ๋นต ์ฒ˜๋ฆฌํ•ด๋†“๊ณ  ๋‚˜์ค‘์— ๋‹ค์‹œ ๋””๋ฒ„๊น…ํ•ด๋ด์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.


๐Ÿ“Œ ํŒ์—…์Šคํ† ์–ด ๋“ฑ๋ก API ๊ตฌํ˜„ ์™„๋ฃŒโ€‹

Store StoreImage StoreCategory ReservationEntry

์–˜๋„ค ์ฒ˜๋ฆฌ ์ˆœ์„œ๊ฐ€ ์ฒ˜์Œ์— ๋ชจํ˜ธํ–ˆ๋‹ค.

์–‘๋ฐฉํ–ฅ ๋งคํ•‘๊ด€๊ณ„๋•Œ๋ฌธ์— Store๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด StoreImage์™€ StoreCategory๋„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•˜๋Š”๋ฐ

StoreImage์™€ StoreCategory ์—ญ์‹œ Store๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ ...

๋ˆ„๊ตฌ๋ฅผ ๋จผ์ €, ์–ด๋–ป๊ฒŒ ์ƒ์„ฑํ•ด์•ผ ํ•˜๋Š”๊ฐ€? ๋ผ๋Š” ์˜๋ฌธ์ด ๋“ค์—ˆ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๋‹ค๋ณด๋‹ˆ, Elasticsearch์—์„œ Store๋ฅผ ์ธ๋ฑ์‹ฑํ•ด์•ผํ•˜๊ณ 

์ธ๋ฑ์‹ฑํ• ๋•Œ StoreImage๋„ ์ˆœํšŒํ•ด์„œ MAIN_THUMBNAIL์„ ๊ฐ€์ ธ๊ฐ€๊ณ 

StoreCategory๋„ CategoryNoDescDto๋กœ ์žฌ๊ฐ€๊ณตํ•ด์„œ ๊ฐ€์ ธ๊ฐ€๋‹ˆ๊นŒ,

์ธ๋ฑ์‹ฑํ•˜๊ธฐ ์ „์—๋Š” ์–˜๋„ค๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์ƒ์— ํ• ๋‹น์ด ๋˜์–ด์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์ž„.

์ •๋ฆฌํ•˜์ž๋ฉด ์ด๋ ‡๋‹ค.


  1. StoreImage, StoreCategory๋งŒ ํ• ๋‹น ์•ˆ๋œ Store ์ƒ์„ฑ -> ๋ฉ”๋ชจ๋ฆฌ ์ƒ์— ์กด์žฌ
  2. EntityManager๋กœ Store๋ฅผ persist -> INSERT ์ฟผ๋ฆฌ ๋‚ ๋ผ๊ฐ (์ด ์‹œ์ ๋ถ€ํ„ฐ auto increment๋œ id ๊ฐ’ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
  3. Store์˜ id ๊ฐ’์œผ๋กœ StoreImage์™€ StoreCategory ์ƒ์„ฑ -> ๋ฉ”๋ชจ๋ฆฌ ์ƒ์— ์กด์žฌ
  4. Store์— StoreImage, StoreCategory setํ•˜๊ธฐ -> ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ ๊ฐ์ฒด์— ํ• ๋‹น
  5. StoreImage ์ €์žฅ (๋‹น์—ฐํžˆ StoreCategory๋Š” ์ €์žฅ ์•ˆํ•จ.) -> INSERT ์ฟผ๋ฆฌ ๋‚ ๋ผ๊ฐ
  6. ReservationEntry ์ƒ์„ฑ ๋ฐ ์ €์žฅ -> INSERT ์ฟผ๋ฆฌ ๋‚ ๋ผ๊ฐ
  7. Elasticsearch ์ธ๋ฑ์‹ฑ -> ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ Store ๊ฐ์ฒด๋กœ ์ฒ˜๋ฆฌํ•จ
  8. @Transactional์— ์˜ํ•ด COMMIT

๊ฒฐ๊ตญ์—”, ๋ฉ”๋ชจ๋ฆฌ ์ƒ์˜ ๊ฐ์ฒด๊นŒ์ง€๋งŒ ํ•„์š”ํ•œ ๋ถ€๋ถ„์ด ์–ด๋Š ์ง€์ ์ธ์ง€,

๊ทธ๋ฆฌ๊ณ  DB์— ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ€์•ผ ํ•˜๋Š” ์ง€์ ์ด ์–ด๋Š ์ง€์ ์ธ์ง€๋ฅผ ๋ช…ํ™•ํžˆ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ–ˆ๋‹ค.

Store๋Š” ๋Œ€๋ถ€๋ถ„์ด @OneToMany๋กœ ์ž์‹์ด ๋งคํ•‘๋œ ๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ์ด๊ณ , ์ž์‹ ์ •๋ณด๊ฐ€ DB์— INSERT ๋‚ ๋ฆด ๋•Œ๋Š” ๋”ฑํžˆ ํ•„์š” ์—†์œผ๋‹ˆ๊นŒ, Store๋ฅผ persistํ•ด์„œ INSERT ์ฟผ๋ฆฌ๋ฅผ ๋จผ์ € ๋‚ ๋ฆฌ๋Š” ๊ฒƒ์ด ์˜ณ์€ ์ˆœ์„œ์˜€๋˜ ๊ฒƒ์ด๋‹ค.

JPA์—์„œ ๋ถ€๋ชจ-์ž์‹ ์—”ํ‹ฐํ‹ฐ ํ•œ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๊ณ ๋Š” ํ•˜๋Š”๋ฐ ์ด๋Š” ๋‚˜์ค‘์— ๋ฆฌํŒฉํ† ๋งํ•ด๋ณด๊ธฐ๋กœ ํ•˜๊ณ 

๋ฌด์‹ํ•˜๊ฒŒ ์ƒ๊ฐํ•˜์ž๋ฉด ์ƒ๋‹จ์˜ ์ € ์ˆœ์„œ๊ฐ€ ์ง๊ด€์ ์ด๋‹ค.

โœจ ๊ตฌํ˜„ ๊ฒฐ๊ณผโ€‹

๊ณ ์ƒํ–ˆ๋‹ค. ๊ทผ๋ฐ resizing ์˜ต์…˜์„ ์ž˜๋ชป ์„ค์ •ํ•œ๋“ฏ ใ…‹ใ…‹ใ…‹ใ…‹

๋ฒ„๊ทธ ๋ฆฌํฌํŠธ... ์ถ”๊ฐ€์š”...

store_image_table

store_reservation_page