Skip to content

[1주차] Database 설계 및 아키텍처 설계 구현 완료#18

Open
dbdb1114 wants to merge 148 commits intohanghae-skillup:mainfrom
dbdb1114:dev
Open

[1주차] Database 설계 및 아키텍처 설계 구현 완료#18
dbdb1114 wants to merge 148 commits intohanghae-skillup:mainfrom
dbdb1114:dev

Conversation

@dbdb1114
Copy link

[1주차] Database 설계 및 아키텍처 설계 구현 완료

  1. 데이터베이스 설계 및 아키텍처 설계,
  2. 메인 페이지 추가
  3. 메인 페이지 상영정보 조회 기능 구현

작업 내용

이번 PR에서 진행된 주요 변경 사항을 기술해 주세요.
코드 구조, 핵심 로직 등에 대해 설명해 주시면 좋습니다. (이미지 첨부 가능)
ex: ConcurrentOrderService에 동시 주문 요청 처리 기능 추가

  • module-rds-repo : DB와의 연결과 entity, repository를 활용합니다.
  • module-service : repository를 이용하여 Data를 가져와 정렬하고, 정제하는 역할을 합니다.
	@Override
	public List<Map.Entry<MovieDTO, List<ShowingDTO>>> getTodayShowing() {
		LocalDateTime from = LocalDateTime.now().plusMinutes(30);
		LocalDateTime to = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);

		List<Showing> showingsByStTimeBetween = showingRepository.findShowingsByStTimeBetween(from, to);

		// 금일 상영 정보가 아예 없을 경우 다음날 조회
		if(showingsByStTimeBetween.isEmpty() || showingsByStTimeBetween.size() < 10){
			LocalDateTime tomorrowFrom = LocalDateTime.of(LocalDate.now().plusDays(1L), LocalTime.MIN);
			LocalDateTime tomorrowTo = to.plusDays(1L);
			showingsByStTimeBetween.addAll(showingRepository.findShowingsByStTimeBetween(tomorrowFrom, tomorrowTo));
		}

		return showingsByStTimeBetween.stream()
			.map(showingEntity -> modelMapper.map(showingEntity, ShowingDTO.class))
			.collect(Collectors.groupingBy(ShowingDTO::getMovie))
			.entrySet().stream()
			.peek(entry -> entry.getValue().sort(Comparator.comparing(ShowingDTO::getStTime)))
			.sorted(Map.Entry.comparingByKey(Comparator.comparing(MovieDTO::getOpenDay, Comparator.reverseOrder())))
			.toList();
	}
  • module-app : module-service에서 조회한 데이터를 응답값에 맞게 수정 보완 처리하여 response를 반환합니다.
	@GetMapping("/today-info")
	public ResponseEntity<Map> getTodayShowing() {
		List<Map.Entry<MovieDTO, List<ShowingDTO>>> todayShowing = showingService.getTodayShowing();

		List<Map<String, Object>> responseData = todayShowing.stream()
			.map(entry -> Map.entry(entry.getKey(), entry.getValue().stream()
				.map(val -> modelMapper.map(val, ShowingResponse.class)).toList()))
			.map(entry -> Map.of("movie", entry.getKey(), "timeTable", entry.getValue()))
			.toList();

		if (responseData.isEmpty()) {
			return ResponseEntity.noContent().build();
		} else {
			return ResponseEntity.ok(Map.of("data", responseData));
		}
	}
  • module-core : dto클래스를 작성했습니다. 구성은 아래와 같습니다.
.
├── dto
│   ├── external
│   │   └── ShowingResponse.java
│   └── internal
│       ├── GenreDTO.java
│       ├── MovieDTO.java
│       ├── RatingDTO.java
│       ├── ScreenDTO.java
│       └── ShowingDTO.java
├── exception
└── util

발생했던 문제와 해결 과정을 남겨 주세요.

  • 문제 1 - 아키텍처를 설계하면서 이정도 기능의 설계에 있어 멀티모듈 아키텍처가 어떤 긍정적인 효과를 가져올지 고민이 많았습니다.
  • 해결 방법 1 - 제가 이해하기로는 결국 멀티모듈 아키텍처를 설계하는 이유도 팀의 불편함, 개인의 불편함에서 시작된 것 같았습니다.
    그래서 제가 어떻게 하면 이전보다 나은 코드와 역할과 책임의 분리를 잘 지킬 수 있을지 고민하여 LayeredArchitecture를 기반으로 multimodule 프로젝트를 설계하게 됐습니다.
  • 문제 2 - 레이어간 역할과 책임의 분리 어느정도 까지인지 컨트롤러의 책임과 역할은 뭐고, 서비스의 책임과 역할은 어느정도 까지인지 고민이 많았습니다.
  • 해결 방법 2 - 무엇을 위해 존재하는가를 생각했습니다. 그래서 컨트롤러는 입출력을 담당하는 부분이라 생각하여, 데이터의 응답 규격에 맞추는 일을 수행하게 했고, 서비스 레이어는 비즈니스적으로 원하는 데이터를 적당히 깔끔한 규격으로 반환하게 하였습니다.

이번 주차에서 고민되었던 지점이나, 어려웠던 점을 알려 주세요.

과제를 해결하며 특히 어려웠던 점이나 고민되었던 지점이 있다면 남겨주세요.

  • 클린 코드에 대한 고민이 많았습니다. 이 메소드명은 적절한지, 뎁스는 적절한지 내 코드만 계속 보고 있으니 가독성이 좋게 느껴지는 건 아닌지 고민이 많았습니다.
  • stream api를 사용하면 데이터를 추가적인 지역변수나 제어문 선언 없이 로직을 처리할 수 있습니다. 코드가 간결해 보이긴 하지만 하다보니 중간 연산의 값을 파악하기 힘든 그런 일들이 있었습니다. 어떻게 사용하는게 좋은지 헷갈렸습니다.
  • 실무에서 어떻게 해야할지 고민이 많습니다. 저는 취준생 입장이라 현업에 나아가서 일을 하려면 지금 프로젝트를 혼자 진행하는 방식과 제 노력과 같은 부분이 적절한지 계속해서 고민했습니다.
  • 커밋 로그를 어떻게 정리해야할지 감이 잘 안 잡힙니다. service레이어를 수정하다보면 Repository 수정이 필요해지고 그러다보면 커밋이 시간흐름을 어기게 되거나 중간중간 service와 repository를 왔다 갔다 하며 난잡해지는 것 같습니다.

리뷰 포인트

  • 최대한 비판적으로 봐주셨으면 좋겠습니다. 고칠점을 서슴없이 말씀해주셨으면 좋겠습니다.
  • stream을 과하게 사용한 것은 아닌지 리뷰 해주셨으면 좋겠습니다.
  • entity설계를 지적해주셨으면 좋겠습니다.
  • 전반적으로 보시기에 약간 거슬리는 부분들도 asis와 tobe가 명확하진 않더라도 듣고싶습니다.

기타 질문

  • 테스트 코드를 제대로 작성하며 프로젝트를 해본 경험이 없습니다. 참고할만한 책이나 강의나 라이브러리를 알려주시면 감사할 것 같습니다.
  • 실무에서 어떻게 일하는지 궁금합니다. 예를 들어 하루에 코드를 작성하는 시간은 몇 시간이나 되는지 코드를 작성하기 위해 설계하는 시간은 얼마나 가지는지 궁금합니다.

추가

늦어서 죄송합니다. 계속 부족한 부분들이 보여 급하게 하다보니,,, 오히려 더 늦어졌습니다. 차주에는 시간을 조금 더 잘 지키도록 하겠습니다.

dbdb1114 and others added 30 commits January 8, 2025 17:11
spring-data-jpa. h2database, postgresql
추가 수정 예상되며, 테스트 혹은 개발하면서 수정 예정
1. BaseEntity 작성하여 상속 하도록 수정 및 빌더 패턴 설정
2. PK Id필드 strategy = GenerationType.IDENTITY 설정 추가
3. Lombok 의존성 추가 및 빌더 패턴 도입
private 필드에 접근하기위한 리플렉션 유틸 메소드
Feature/rds repo 1주차 요구사항 rds-repo 작업 완료
1. Orderby RDB에서 하는 게 무의미함
2. 메소드 변경에 따른 테스트 코드 수정 및 Assertions 메소드 수정
테스트 코드 작성시 빈을 로드하는 context 지점을 최상위로 둘 수 있어야 하기 때문에
java.main.module 패키지 생성후 기존 패키지 모두 하위로 이동
implementation: 모듈, model-mapper
compileOnly : lombok, starter-web, data-jpa
stream 활용하여 상영정보를 영화 기준으로 groupby하고, 정해진 기준으로 정렬
( 최신 개봉을 앞으로 정렬, 상영시간은 가장 이른 시간을 앞으로 정렬 )
1. �의존 주입 확인
2. groupBy 및 금일 기준 조회 여부 확인
dbdb1114 and others added 30 commits January 27, 2025 07:15
티켓 조회, 유저 티켓 조회, 티켓 예매 요청
AopForTransaction : 트랜잭션 분리를 위한 클래스
CustomSpringELParser :  SPEL표현식 파서
DistributedLock : 분산락 어노테이션 정의
DistributedLockAOP : aop 분산락 작성
FunctionalForTransaction : 트랜잭션 분리를 위한 클래스
FunctionalDistributedLock : 함수형 분산락 구현
PEAK 타임을 고려한 최대 동시간대 요청 건수 계산

----------------------------------------------------------
Junghyun's hanghaeho

인기영화 100편
비인기 영화 489편
모든 영화 1일 1회씩 상영, 2일 뒤 상영정보까지 전시

----------------------------------------------------------

인기 영화 100편의 경우
상영정보 오픈과 동시에 반 정도의 티켓이 바로 판매된다고 가정
100 * 13 = 1300건

비인기 영화 489편의 경우
상영정보 오픈과 동시에 약 3매 티켓 정도 바로 판매된다고 가정
489 * 3 = 1467건

피크시간 전체 판매 티켓 수 : 2767건

----------------------------------------------------------

예매는 한 사람이 여러좌석 예매 가능하므로
피크시간 예상 요청건수 = 922.33333 건 (전체 판매 티켓 수/3)
상영정보 조회 및 선택 - 좌석 선정 프로세스 소요시간 : 최대 30초 추정

----------------------------------------------------------

테스트상 30초까지 점진적으로 1000건의 요청시 최대 요청 처리시간 : 483.43ms

- waitTime 동안 Lock을 기다릴 이유는 무엇인가?
먼저 좌석을 선정한 고객이 불가피한 이유로 결제 과정까지 넘어가지 못 했을 때

결제 과정까지 넘어가지 못하는 상황
1. 로그인, 연령 부적합 등 애초에 좌석 선정 자체가 불가한 경우
2. 결제 중 이탈
3. 단순 변심
4. ...

사실상 1번 말고는 기다렸다가 재시도 해야하고, 1번의 경우 매우 적은 수일 것으로 예상되어
실제로 Lock 점유를 기다리는 시간이 그다지 길 필요가 없다는 판단이 됩니다.
최악의 경우를 생각하여 앞의 요청 처리 두 건에서 lock점유 후 다음 프로세스로 진행이 못 했을 때 까지만 보장하기로 하여, wait_time은 1초, lease_time은 2초로 선정

lease_time 선정 이유는 만약 여러 좌석 lock 중 일부 좌석에 대한 lock을 대기하여야 할 수 있으므로 wait_time을 고려한 선정
core module - SpringBootStarterWeb 의존성 추가 [ Custom Exception 정의 관련 ]
app module - aop 및 limiter 관련 의존성 추가
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants