묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
패키지 구분에 대해 궁금한게 있습니다
요즘은 domain별로 패키지를 나눈다고 들었는데 강의에서는 역할별로 패키지를 나누고 있어서요.어떻게 나누는게 좋은건가요?!\
-
미해결스프링 핵심 원리 - 기본편
코드 자료
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]강의 자바 코드 자료는 따로없을까요?
-
해결됨6주 완성! 백엔드 이력서 차별화 전략 4가지 - 똑같은 이력서 속에서 돋보이는 법
조회속도 개선에서 더 개선하는 방법이 궁금합니다.
안녕하세요. 인덱스를 활용한 조회 성능 개선을 공부하던 중 궁금한 점이 생겨 질문드립니다.현재 저는 OFFSET 기반 pagination을 사용하는 서비스를 개발하고 있으며, 다음과 같은 환경에서 성능 테스트를 진행했습니다.데이터: 약 1,000만 건서버: EC2 t3.smallDB: RDS t4g.microk6 vus1001. 문제 상황초기에는 OFFSET 제한 없이 마지막 페이지까지 이동 가능하도록 구현했습니다.하지만 데이터가 1,000만 건 수준으로 증가하자, 깊은 페이지로 갈수록 조회 속도가 급격히 느려지는 문제를 확인했습니다.2. 고민 및 제약일반적으로 이 문제는 Keyset Pagination(커서 기반)으로 해결하라고 많이 알려져 있습니다.하지만 제 서비스는👉네비게이션 바를 통한 페이지 직접 이동 (ex. 1, 10, 100 페이지 클릭)이 필요하기 때문에 Keyset 방식만으로는 요구사항을 만족시키기 어렵다고 판단했습니다.3. 적용한 개선 방법다음과 같은 방식으로 성능 개선을 진행했습니다.OFFSET 최대 범위를 제한 (최대 10,000 페이지 / OFFSET 100,000)커버링 인덱스 적용조회 방식 개선먼저 ID만 조회 → 이후 필요한 10건만 상세 조회전체 게시글 수(count)는 캐싱 처리4. 성능 개선 결과[Page 10] avg: 1.4s → 700ms p95: 4.5s → 1.8s [Page 100] avg: 17s → 1.18s p95: 24s → 3.3s [Page 1000] avg: 32.1s → 1.7s p95: 59s → 4.27s SQL 쿼리는 분석결과 약 1700MS -> 70MS 까지 단축한 것 같습니다.5. 추가 제약사항로그인 사용자와 비로그인 사용자의 조회 결과가 다름(사용자별 구독 게시글이 포함됨)따라서 캐시는 비로그인 사용자에만 적용위 성능 수치는 로그인 사용자 기준6. 현재 고민위와 같이 개선했지만,👉 여전히 성능이 충분하지 않다고 느끼고 있습니다.특히 궁금한 점은 다음과 같습니다.7. 질문OFFSET 기반 pagination을 유지하면서👉추가로 성능을 개선할 수 있는 방법이 있을까요?다음과 같은 방법들을 고려했는데, 방향성이 맞는지 궁금합니다.RDS를 2개를 사용하여 조회 성능 데이터를 각각 2개의 db가 처리하도록 한다?Keyset + OFFSET 혼합 방식(일반적인 페이지 이동은 Keyset Pagination을 사용하고,사용자가 특정 페이지를 직접 입력하거나 점프하는 경우에만제한적으로 OFFSET 기반 조회를 사용하는 혼합 방식)RDS 스펙 업그레이드또한 에펨코리아(https://www.fmkorea.com/)와 같은 대형 커뮤니티는 제가 원하는 페이지 네이션 방식을 사용하면서 깊은페이지(최대 1만)도 지원하고동시접속자 수십만페이지 수천~수만대량 데이터환경에서도 빠른 조회 성능을 유지하는데👉이러한 서비스들은 어떤 방식으로 pagination 및 조회 성능을 처리하는지 궁금합니다.
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
servlet과 container에 대한 질문입니다
안녕하세요.servlet에 대해서 공부하고 있는데 servlet container는 servlet의 생명주기를관리한다라고 알고 있습니다. 그런데 controller 같은 경우는 spring 컨테이너에서 관리하는 것으로 알고 있습니다.그러면 servlet container는 dispatcherservlet만 관리하고 controller는 spring container에서 관리하는 걸까요? 그리고 servlet, controller의 차이는 무엇인가요?
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
RepositoryTest의 패키지 위치가 domain인 이유
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요, 영한님.ItemRepositoryTest 파일의 위치가 hello.iitemservice.repository가 아니라, hello.iitemservice.domain임을 확인했습니다.제 생각으로는 단순 패키지 구조 오타로 인해 domain에 위치된 것이라고 여겨지는데요. 혹시 의도적으로 domain 패키지 하위에 두신 건지 궁금합니다!
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
도메인 모델에서 관계와 규칙을 구분하는 방법
안녕하세요. 도메인 모델을 설명하는 부분에서 관계와 규칙을 온라인 서점 운영 예시로 간단히 언급해주셨습니다.강의를 들으면서 실제 업무 도메인에 관계와 규칙을 구분지으며 간단히 개인 실습을 해보았는데요.적다보니 어느샌가 관계에 규칙이 섞이기도 하더라고요. 문득 이런 생각이 들었습니다. '관계와 규칙의 차이는 무엇인가?'관계와 규칙을 명확하게 구분지으려면 어떤 기준을 갖고 생각해야할까요? 관계는, DB 모델에 워낙 익숙하다보니 하나의 OO은 여러 OOO을 갖는다. 이쪽으로 먼저 생각이 흐르기도 하고요. 예시로 들어주신 것을 보면 관계는, '비즈니스에서 관계'라는 생각이 듭니다. 규칙은 데이터를 변경할 때 필요한 조건이라고 생각하면 될까요?토비님 의견이 궁금합니다.
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
UserService, CertificationService 책임 분리 기준 질문
UserService 가 회원 생성이라는 유즈케이스를 담당하는데, 인증 url 생성이나 메일 발송 정책까지 함께 가지고 있기 때문에 책임이 섞여있다고 판단하여 CertificationService 를 분리하신걸까요? 단순히 외부 의존성 분리하려는 목적보다는 인증메일 정책 이라는 별도의 변경 이유를 분리하려는 의도가 맞는지 궁금합니다
-
미해결스프링 DB 1편 - 데이터 접근 핵심 원리
spring initialiser 어떤걸 선택해야될지 모르겠어요
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)예[질문 내용]여기에 질문 내용을 남겨주세요. 안녕하세요.spring db 1편 들으려 하는데초기 설정이 강의랑 많이 다르네요Gradle groovy, Gradle kotlinSpring boot (4.05 vs 3.5.13)java 21 vs 17등 어떤 것을 선택해야할지 모르겠어요
-
미해결스프링 핵심 원리 - 기본편
구현체가 동적으로 정해질 때, 팩토리 기법을 사용하나요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]1. 현재 FixDiscountPolicy와 RateDiscountPolicy는 동적으로 바뀌는 것이 아니라, 만약 실행 중에 정책이 바뀌면 서버를 끄고, 코드를 수정하고 다시 올려야 합니다. 하지만 만약, 사용자가 동적으로 둘 중에 선택을 할 수 있다고 가정한다면은, 즉 FixDiscountPolicy와 RateDiscountPolicy 모두 @Bean으로 관리되고 사용자의 선택으로 어떤 구현체가 쓰일 것인지 정해진다면, 팩토리 기법으로 사용하는게 올바른 것일까요? 예를 들어 discountPolicyFactory가 생성될 때, discountPolicy 인터페이스를 구현한 구현체들 모두 map(빈 이름, 빈 객체) 형태로 Factory에 등록한 후에, 사용자가 선택할 때마다 FixDiscountPolicy라는 빈 객체가 반환되도록 하는 것입니다.2. 혹은 요즘 실무에서는 이런 상황에서 팩토리 기법 말고 또 다른 방법이 존재하는지에 대해서도 여쭤보고 싶습니다.감사합니다.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
Json 요청 처리
형 나 지금 너무 재밌어서 잘 따라하던중 별거 아닌거에서 막혀서 좀 힘들어형이 알려준 json 파라미터 넘기는 방법으로 해도 안되고 gpt 찾아서 진행 한거도 다 안돼는데 윈도우 환경에서 좀 힘든걸까 ?? 다른 수강생분이 올려준거도 봤는데 답변에 달아준 방법도 동작하지 않아 @Bean public JsonJobParametersConverter jobParametersConverter() { return new JsonJobParametersConverter(); } @Bean @StepScope public Tasklet terminatorTasklet( @Value("#{jobParameters['infiltrationTargets']}") String infiltrationTargets ) { return (contribution, chunkContext) -> { String[] targets = infiltrationTargets.split(","); log.info("⚡ 침투 작전 개시"); log.info("첫 번째 타겟: {} 침투 시작", targets[0]); log.info("마지막 타겟: {} 에서 집결", targets[1]); log.info("🎯 임무 전달 완료"); return RepeatStatus.FINISHED; }; }
-
미해결스프링 시큐리티 완전 정복 [6.x 개정판]
로그아웃-logout()-2 강에서 겟방식 로그아웃 호출 후 화면이동 질문입니다.
8분 29초에서 /logoutProc 를 겟방식으로 호출했고 로그아웃은 성공했습니다.로그아웃 성공후 /logoutSuccess 로 이동해야 하고 해당 url 은 permitAll() 입니다. 그런데 왜 로그인화면이 표시되는지 궁금합니다.강의를 조금 더 보다보니, 궁금증은 해결됐습니다. 그런데 여전히 궁금한 점은 해당 소스에 맨 아래 permitAll() 의 역할입니다. 실제 아무일도 안하는건지 궁금합니다.
-
해결됨6주 완성! 백엔드 이력서 차별화 전략 4가지 - 똑같은 이력서 속에서 돋보이는 법
Build 관련 문제 (테스트 관련 문제)
다른 분들에게 도움이 될까 글을 작성합니다. 저는 윈도우 환경에서 InteliJ를 사용하고 CLI 화면이 편하기 때문에 WSL를 사용하여 도커를 사용했습니다. 해당 전에 문제 해결들은 자료가 없어서 해결 방안만 말씀드리겠습니다.cloud... gradle 문제 해당 프로젝트가 One Driver에 있기 때문에 클라우드 상에 있는 그레이들이 안되는 것으로 알고 있습니다. 만약 프로젝트가 One Driver에 있다면 One driver 밖으로 이동 시켜주세요WSL 도커를 실행해도 윈도우 환경에서는 컨테이너를 찾지 못하는 경우가 있기 때문에 Window 환경에서 도커를 실행 하세요 해당 예외들이 터진 후에 모든 테스트 로직에 대해 예외가 발생합니다.BackendportfolioApplicationTests > contextLoads() FAILED java.lang.IllegalStateException at DefaultCacheAwareContextLoaderDelegate.java:143 Caused by: java.lang.IllegalStateException at LoadingCache.java:75 Caused by: java.lang.ExceptionInInitializerError at Class.java:-2 Caused by: java.lang.IllegalStateException at DockerClientProviderStrategy.java:277원래 전에는 build와 테스트가 잘 진행되었는데 무슨 일인지 Test에서 도커를 만들지 못하는 문제가 생겼나 봅니다. 저는 테스트에서 사용되는 DB Config들을 사용하는 곳에 주석 처리하고 도커 컴포즈로 DB를 주석 처리하고 테스트에서 사용되는 DB들은 Docker compose에 사용되는 DB를 켜서 사용했습니다. Docker Compose에서 사용되는 DB로 사용되기 싫으시다면 Docker 컨테이너로 따로 만드시면 될 것 같아요 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @SpringBootTest //@Import({TestDatabaseConfig.class, TestRedisConfig.class}) public @interface IntegrationTest { } docker compose up db redis -d build 관련 에러들은 어노테이션 설정, gradle 설정, 컴파일 설정 등 많은 이유가 있어 하루 종일 붙잡아도 문제 해결이 안되는 점이 많아 시간으 며칠 잡아 먹었네요 글을 깔끔하게 가독성 좋게 작성하지 못해 아쉽지만 다른 사람들이 똑같은 문제를 맞았을 때 해당 글이 도움이 되길 바랍니다.
-
해결됨카카오 면접관의 실무 밀착형 Spring Batch: 대용량 데이터 처리의 모든 것
job, step execution 관련 질문 드립니다.
안녕하세요.잡이 어떻게 스텝에서 사용하는 컨텍스트 값까지 가지고 있는지 잘 이해가 되지 않아 질문드립니다.분명 JobExecutionContext에 넣은 것이 아니라 StepExecutionContext에 값을 저장했는데, 확인해보니 JobExecutionContext에도 동일하게 저장된 것처럼 보여서 헷갈렸습니다.제가 이해한 바로는 JobExecutionContext와 StepExecutionContext는 서로 다른 영역이고,JobExecutionContext는 step 간 공유용, StepExecutionContext는 해당 step 전용으로 알고 있습니다.그런데 왜 StepExecutionContext에 넣은 값이 JobExecutionContext에도 같은 형태로 보이는지 잘 모르겠습니다.
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
이미지 업로드와 db 트랜잭션 묶는법
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]상품 리뷰 등록·수정·삭제 시 AWS S3 같은 외부 스토리지에 이미지 업로드/삭제가 함께 발생하는데,DB 트랜잭션(리뷰 저장/삭제)과 S3 작업(이미지 업로드/삭제)은 서로 다른 시스템이라 하나의 트랜잭션으로 묶기 어렵습니다. 실무에서는 리뷰 저장/수정/삭제와 이미지 업로드/삭제를 어떤 순서와 전략으로 설계하는지 궁금합니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
sdk 설정 오류
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.이렇게 오류가 떠서 jdk oracle을 다운 받고sdk도 설정하고IVM도 설정했습니다. 자바 스프링 버전은 최신 버전인 4.0.5 대신 강의 자료에서 3.x인 최신버전을 선택하라길래 3.5.13.을 선택했습니다. 무슨 문제일까요?
-
해결됨카카오 면접관이 알려주는 MSA 관점에서의 분산 트랜잭션 패턴
Orchestration SAGA 패턴 보상에 대한 질문입니다.
Orchestration SAGA 패턴 구현에 대해 고민하다가 질문이 생겨 남깁니다.보상을 요청하는 메서드가 명시적으로 나와있어 호출할 때(동기로 호출)만약 rollback을 요청하는 호출이 실패하게 된다면 이후의 순서대로 service에 보상 요청을 하는 동작을 멈춰야 할지 계속 진행하는 게 바람직할지 고민이 됩니다.예를 들어 서비스 1,2,3,4가 있고 center server가 orchestration 관리를 하고 1,2,3,4 순서대로 서비스 호출해서 관리를 진행한다고 했을 때 3에서 장애가 발생해서 2를 롤백하던 중 2 롤백에서 예외가 발생해서 롤백에 실패한 경우 orchestration에서 1에 대한 롤백을 진행해줘야 할지 아니면 일단 멈춰야 할지 고민입니다. 고민의 이유는 순서대로 롤백을 해주는 것은 앞에 작업이 뒤의 작업에 의존성이 있을 때만 그렇게 해주면 되나에 대한 고민이 있었습니다. 두 롤백 간에 데이터 의존성이 없다면 괜찮지 않을까 고민했습니다.다음으로 일단 1도 롤백을 한다면 어디서부터 어디까지 롤백이 진행됐는지 추적이 어려워지지 않을까 고민했습니다. 롤백을 어떤 것은 해주고 어떤 것은 안해준다면 어디까지 롤백했는지 추적이 힘들어지지 않을까 생각이 들었습니다.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[건의][6장][작전1] deprecated 메소드
형 ... @Bean public Step threatAnalysisStep( JpaPagingItemReader<Human> humanThreatDataReader, ItemProcessor<Human, TargetPriorityResult> threatAnalysisProcessor, FlatFileItemWriter<TargetPriorityResult> targetListWriter ) { return new StepBuilder("threatAnalysisStep") .<Human, TargetPriorityResult>chunk(10, transactionManager) .reader(humanThreatDataReader) .processor(threatAnalysisProcessor) .writer(targetListWriter) .taskExecutor(taskExecutor()) // deprecated 되어서 안씀 // .throttleLimit() .build(); } ... 에서 .throttleLimit() 부분이 5 이후로 deprecated 된다고 명시되어 있어. 관심사 분리로 인해서 taskExecutor 정의 부분에서 설정하라고 권장하는 것을 확인했거든. 현재 강의가 사실상 Spring Batch 5.x 까지의 기본 내용과 원리를 살펴봤잖아 ? 그렇다 하더라도 혼란을 줄 수 있기 때문에 해당 부분은 삭제하던지 아니면 추가 내용을 기재해야 할 것으로 보여.
-
미해결죽음의 Spring Batch: 새벽 3시의 처절한 공포는 이제 끝이다.
[예제][3장][작전2] windows 에서 마지막 예제
... @Slf4j @Configuration @RequiredArgsConstructor public class LogProcessingJobConfig { private final JobRepository jobRepository; private final PlatformTransactionManager transactionManager; private final String BASE_PATH = "C:\\Users\\user\\Desktop\\batch\\section3\\flatfileitemwriter"; @Bean public Job logProcessingJob( Step createDirectoryStep, Step logCollectionStep, Step logProcessingStep ) { return new JobBuilder("logProcessingJob", jobRepository) .start(createDirectoryStep) .next(logCollectionStep) .next(logProcessingStep) .build(); } @Bean public Step createDirectoryStep(SystemCommandTasklet mkdirTasklet) { return new StepBuilder("createDirectoryStep", jobRepository) .tasklet(mkdirTasklet, transactionManager) .build(); } @Bean @StepScope public SystemCommandTasklet mkdirTasklet( @Value("#{jobParameters['date']}") String date ) { SystemCommandTasklet tasklet = new SystemCommandTasklet(); tasklet.setWorkingDirectory(BASE_PATH); String collectedLogsPath = "collected_ecommerce_logs\\" + date; String processedLogsPath = "processed_logs\\" + date; try { tasklet.setCommand("cmd.exe", "/c", "mkdir", collectedLogsPath, processedLogsPath); tasklet.setTimeout(3000); } catch (Exception e) { log.error("mkdirTasklet error: ", e); } return tasklet; } @Bean public Step logCollectionStep(SystemCommandTasklet scpTasklet) { return new StepBuilder("logCollectionStep", jobRepository) .tasklet(scpTasklet, transactionManager) .build(); } @Bean @StepScope public SystemCommandTasklet scpTasklet( @Value("#{jobParameters['date']}") String date ) { SystemCommandTasklet tasklet = new SystemCommandTasklet(); tasklet.setWorkingDirectory(BASE_PATH); String processedLogsPath = "collected_ecommerce_logs\\" + date; StringJoiner commandBuilder = new StringJoiner(" & "); for (String host : List.of("localhost")) { // Windows 환경에서 해당 코드를 적용하려면 ssh 관련 설정들을 찾아야 해서 주석 처리 // String command = String.format("scp %s:~/ecommerce_logs/%s.log %s\\%s.log", host, date, processedLogsPath, host); String sourcePath = BASE_PATH + "\\ecommerce_logs\\" + date + ".log"; String command = String.format("copy %s %s\\%s.log", sourcePath, processedLogsPath, host); commandBuilder.add(command); } tasklet.setCommand("cmd.exe", "/c", commandBuilder.toString()); tasklet.setTimeout(10000); return tasklet; } @Bean public Step logProcessingStep( MultiResourceItemReader<LogEntry> multiResourceItemReader, LogEntryProcessor logEntryProcessor, FlatFileItemWriter<ProcessedLogEntry> processedLogEntryJsonWriter ) { return new StepBuilder("logProcessingStep", jobRepository) .<LogEntry, ProcessedLogEntry>chunk(10, transactionManager) .reader(multiResourceItemReader) .processor(logEntryProcessor) .writer(processedLogEntryJsonWriter) .build(); } @Bean @StepScope public MultiResourceItemReader multiResourceItemReader( @Value("#{jobParameters['date']}") String date ) { MultiResourceItemReader<LogEntry> resourceItemReader = new MultiResourceItemReader<>(); resourceItemReader.setName("multiResourceItemReader"); resourceItemReader.setResources(getResources(date)); resourceItemReader.setDelegate(logFileReader()); return resourceItemReader; } private Resource[] getResources(String date) { try { String formattedBasePath = BASE_PATH.replace("\\", "/"); String location = "file:///" + formattedBasePath + "/collected_ecommerce_logs/" + date + "/*.log"; PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); return resolver.getResources(location); } catch (IOException e) { throw new RuntimeException("Failed to resolve log files", e); } } @Bean public FlatFileItemReader<LogEntry> logFileReader() { return new FlatFileItemReaderBuilder<LogEntry>() .name("logFileReader") .delimited() .delimiter(",") .names("dateTime", "level", "message") .targetType(LogEntry.class) .build(); } @Bean public LogEntryProcessor logEntryProcessor() { return new LogEntryProcessor(); } @Bean @StepScope public FlatFileItemWriter<ProcessedLogEntry> processedLogEntryFlatFileItemWriter( @Value("#{jobParameters['date']}") String date ) { String outputPath = Paths.get(BASE_PATH, "processed_logs", date, "processed_logs.jsonl").toString(); ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))); objectMapper.registerModule(javaTimeModule); return new FlatFileItemWriterBuilder<ProcessedLogEntry>() .name("processedLogEntryJsonWriter") .resource(new FileSystemResource(outputPath)) .lineAggregator(item -> { try { return objectMapper.writeValueAsString(item); } catch (JsonProcessingException e) { throw new RuntimeException("Error Converting item to JSON", e); } }) .build(); } @Data public static class LogEntry { private String dateTime; private String level; private String message; } @Data public static class ProcessedLogEntry { private LocalDateTime dateTime; private LogLevel level; private String message; private String errorCode; } public enum LogLevel { INFO, WARN, ERROR, DEBUG, UNKNOWN; public static LogLevel fromString(String level) { if (level == null || level.trim().isEmpty()) { return UNKNOWN; } try { return valueOf(level.toUpperCase()); } catch (IllegalArgumentException e) { return UNKNOWN; } } } public static class LogEntryProcessor implements ItemProcessor<LogEntry, ProcessedLogEntry> { private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME; private static final Pattern ERROR_CODE_PATTERN = Pattern.compile("ERROR_CODE\\[(\\w+)]"); @Override public ProcessedLogEntry process(LogEntry item) throws Exception { ProcessedLogEntry processedLogEntry = new ProcessedLogEntry(); processedLogEntry.setDateTime(parseDateTime(item.getDateTime())); processedLogEntry.setLevel(parseLevel(item.getLevel())); processedLogEntry.setMessage(item.getMessage()); processedLogEntry.setErrorCode(extractErrorCode(item.getMessage())); return processedLogEntry; } private LocalDateTime parseDateTime(String dateTime) { return LocalDateTime.parse(dateTime, ISO_FORMATTER); } private LogLevel parseLevel(String level) { return LogLevel.fromString(level); } private String extractErrorCode(String message) { if (message == null) { return null; } Matcher matcher = ERROR_CODE_PATTERN.matcher(message); if (matcher.find()) { return matcher.group(1); } // ERROR 문자열이 포함되어 있지만 패턴이 일치하지 않는 경우 if (message.contains("ERROR")) { return "UNKNOWN_ERROR"; } return null; } } } 윈도우즈에서는 scp 명령어 관련해서 windows 서비스에서 open-ssh-server 실행현재 사용하고 있는 사용자(windows 사용자) 가 관리자 권한을 보유 할 경우 programdata/sshd_config 파일 수정 필요기타 파일 권한 정리 필요로 인하여 scp 프로그램을 windows 환경의 intellij idea 에서 id, 비번치게끔 ide 콘솔에서 지원하지 않는거 같아. 그래서 제미니가 추천해준대로 실습 환경에서 scp 접속 단을 copy 로 그냥 퉁치는 것으로 바꿔놨어. 나처럼 windows 환경인 사람들이 있으면 혹시나 참고했으면 좋겠네. ObjectMapper, JavaTimeModule 같은 경우는 org.springframework.boot:spring-boot-starter-json 의존성 추가 필요
-
해결됨서버개발자 과제전형 완벽가이드 - 1편
NaverBookRepository.class 의 위치에 따른 모듈간의 의존성에 대해 질문드립니다.
학습관련 질문을 남겨주세요! 상세히 작성주시면 더 좋아요 🙂 안녕하세요, 강사님 🙂이번 질문도 혼자 먼저 구현 후, 강사님의 강의를 들으며 리팩토링을 진행하며 궁금증이 하나 생겨 질문 드립니다. 저는 search-api 모듈의 com.library.repository의 NaverBookRepository.class 위치를 강사님과 다르게 external:naver-client 에 위치시켰습니다.(NaverBook/KakaoBook은 external 에 종속된 구현체라고 생각) 그러다 보니, naver-client 모듈이 search-api 모듈에 있는 BookRepository, PageResponse, SearchResponse 를 알아야 했으며, naver-client 모듈이 search-api 모듈을 implementation('...') 으로 의존?참조? 하게 되었습니다. // search-api 모듈 public interface BookRepository { PageResponse<SearchResponse> search(...); } 이렇게 external -> search-api 로 흘러가는 의존 방향에 이질감이 듭니다.(외부 API 가 app 을 안다..?) 그래서 모듈간 의존 방향이 search-api -> external 되도록 강사님 강의처럼 NaverBookRepository 를 search-api 모듈에 위치 시켜도 NaverClient 때문에 search-api에 feign 종속성이 생긴 다는 것이 모듈간 의존성 격리가 되지 않아서 멀티모듈의 장점이 퇴색되었다고 생각합니다. 강사님은 과제가 아닌 실무에서 search-api 와 external 간의 모듈 격리를 어떻게 하시며, 의존 방향을 어떻게 하시는지 궁금합니다. 저의 부족한 지식을 확장할 수 있게 강사님의 인사이트 공유해주시면 감사하겠습니다!
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
Could not resolve org.springframework.boot:spring-boot-starter-validation:2.4.4
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]eclipse 환경에서 validation 예제를 따라가고 있었는데 Bean Validation 강의부터 라이브러리가 다운받아지지 않습니다.Could not resolve: org.springframework.boot:spring-boot-starter-validation:2.4.4위와 같은 메세지가 출력되면서 외부 의존성 라이브러리를 확인해봤을 때, jakarta.validation-apihibernate-validator 라이브러리가 추가되지 않는 것을 확인했습니다. 검색해도 해결방안을 찾지 못해 질문 남깁니다. 어떻게 해결할 수 있을까요? 추가로 Eclipse는 4.16.0 버전 사용중입니다.