07/04 ~ 07/10 기간 동안의 회고록이다.

 

한 일

  • 휴식
  • 이력서 작성
  • 1일 1 알고리즘

 

잘한 점

  • 휴식을 한 것
  • 이력서의 필요성을 느끼고 이력서를 작성한 것

 

아쉬운 점

  • 코로나에 걸린 것
  • 입맛이 없어 밥을 잘 안먹는 것

 

다음 주 목표

  • 사이드 프로젝트
  • CS공부
  • 토비의 스프링 읽기
  • 1일 1 알고리즘

이번 주, 나의 생각

 

 코로나 확진판정을 받아서 일주일 간 격리했다. 오랜만에 게임도 하고 넷플릭스, 유튜브도 보면서 놀았다. 수요일부터는 컨디션이 회복되어서 지난 주에 계획한대로 이력서를 작성했고 이 외에는 모두 휴식을 취했다. 한 주간 아무생각없이 놀다보니까 컨디션이 돌아와서 다음 주 부터는 다시 사이드프로젝트, 미뤄둔 개인 공부를 진행해보려고 한다. 이제는 강제되는 미션이나 방향성을 제시해주던 학원이 없으므로 나 스스로 무엇을 할 지 생각해보고 취업까지 꾸준히 해 볼 생각이다.

06/13 ~ 07/01 기간 동안 진행한 issue-tracker 프로젝트 회고록

 

코드스쿼드의 마지막 프로젝트로 3주 간 BE 2명과 FE 2명이 함께 issue-tracker 프로젝트를 진행했다. issue-tracker는 GitHub Repository의 issue 기능을 만드는 프로젝트로 이슈 필터 기능을 중점적으로 구현하고자 했다.

 

프로젝트 저장소


미션에서 한 일

  • 요구사항 분석, 도메인 설계, 테이블 설계, API 설계
  • MockService를 통한 MockAPIServer 구현
  • Swagger를 통한 API 문서 작성
  • VPC 서버 아키텍처
  • GitHub Actions와 CodeDeploy를 통한 CI/CD
  • 로그인 기능
    • JWT + GitHub OAuth를 통한 로그인 기능
    • Redis를 통한 Refresh token 관리
    • annotation을 이용한 로그인 인증
  • S3를 이용한 파일 업로드 기능
  • Spring Data JPA, QueryDSL
  • 이슈 필터링을 위한 동적쿼리
  • Paging
  • 삽질기록
  • 학습정리 기록

 


😍 좋았던 것

  • 좋은 프로젝트 멤버들

프로젝트 멤버 모두 올빼미족이라서 데일리 스크럼을 첫날 제외하고 오후 5시 30분에 진행했다. 매일 BE 진행상황, FE 진행상황을 공유하면서 API 문서에 대해 토론도 진행했다. 매일 진행상황을 공유하면서 개발 속도를 맞출 수 있어서 좋았다. 또 프론트와 협업이 많이 필요한 로그인 기능을 구현할 때는 같이 모여서 충분히 토론한 이후에 구현을 진행하는 등 소통이 잘 되어서 좋았다. 이 과정에서 너무 백엔드 중심의 API설계를 하거나 잘못된 로그인 flow를 프론트와 토론하는 과정에서 바로잡을 수 있었고 클라이언트의 시각에서 다시 한 번 생각해 볼 수 있어서 좋았다. 또 클라이언트의 입장에서 생각하기 위해서는 클라이언트에 대한 지식이 있어야 겠다는 생각을 하게 되었다. 나중에 간단하게 프론트 공부도 해야겠다.

 

  • ERD 설계

프로젝트 첫 날 요구사항 분석, 기본 설계를 진행했다. 아직 테이블 설계나 ERD 설계에 미숙한 점이 많은데 같이 프로젝트를 진행하는 후가 도움을 많이 줘서 같이 기본 설계를 수월하게 진행할 수 있었다. ERD 설계가 있으니 프로젝트의 규모나 구조가 더 잘 보이고 내가 지금 무엇을 하는지 확실하게 인지하면서 프로젝트를 진행할 수 있어서 좋았다.

 

  • Mockup API Server (Swagger + MockService)

지난 미션에서는 postman을 통한 Mockup API Server를 구축했었다. 프로젝트를 진행하면서 API를 변경하는 일이 생기면 코드 레벨에서 고치고 postman에서 API 문서를 한 번 더 고쳐야 한다는 불편함이 있었다. 이때문에 바로바로 수정을 하지 못해서 지난 미션에서는 Mockup API Server와 서비스하고 있는 실 서버의 API가 다른 경우가 있었다. 이에 코드레벨에 가깝게 Mockup Server를 두어야 한다고 판단하여 MockService를 구현하여 자바에서 Mock Data를 직접 생성한 후 빠르게 배포를 하여 Mockup API Server를 만들었다. 이 때 Swagger도 연동을 하여 코드 레벨로 API 문서를 관리하니까 API의 변경이 있어도 바로 반영을 할 수 있었다.

 

  • 페어 프로그래밍

같은 BE 멤버인 후와 많은 과정을 페어 프로그래밍으로 진행했다. Intellij의 code with me를 통해서 페어 프로그래밍을 하거나 협업을 했다. 코드를 작성하면서 의견이 다른 부분은 사소한 것 하나까지 서로의 입장을 이야기해보고 무엇이 더 좋을지에 대해 이야기를 많이 나누었는데 이 과정에서 나와 다른 시각을 배울 수 있어서 좋았다.

 

  • 학습 정리 기록 및 토론

처음 써보는 기술 또는 공부가 필요한 키워드들을 적어 놓고 각자 공부한 이후에 노션에 정리한 후 서로 설명을 했다. 이 과정에서 혼자 공부하는 것보다 훨씬 더 많이 배울 수 있어서 좋았다.

 

  • 새로운 기술 학습

이번 프로젝트를 진행하면서 파일 S3 업로드 후 링크 반환, CORS, Redis, CodeDeploy, annotation을 통한 인증 처리 등 새로운 기술과 기능을 적용해보았다. 새로운 기술을 학습하고 적용하는 것을 계속 반복하다 보니 점점 익숙해지고 어떻게 해야 할지 감이 잡히는 것 같다.

 


📚 배운 것

  • CORS

처음으로 프론트엔드와 작업을 하게 되면서 CORS 문제를 해결해야 했다. 스프링이 간편하게 CORS 문제를 해결해주지만 CORS가 무엇이고 왜 서버에서 해결을 해야 하는지 궁금해서 이와 관련되어 공부를 진행했고 프로젝트에서 CORS 문제를 해결하도록 설정해주었다.

 

  • QueryDSL에서 동적쿼리 & MultipleBagFetchException 해결

이전에도 QueryDSL을 사용했었지만 이 번 미션처럼 많은 엔티티를 fetch join하고 동적쿼리를 사용해야 할 상황이 없었다. 이번 미션을 진행하면서 N+1 문제를 해결하고자 1 : N 관계에 있는 엔티티 여러 개를 fetch join 해야 했었는데 MultipleBagFetchException이 발생하였었다. QueryDSL에서는 한 개의 1 : N 관계만을 fetch join할 수 있으므로 자료구조를 List가 아닌 Set으로 변경하여 여러 1 : N 관계를 fetch join하여 N + 1 문제를 해결했다. 또 굉장히 많은 조건을 동적쿼리로 만들어 볼 수 있어서 QueryDSL 사용법을 조금 더 익힌 것 같다.

 

  • Redis

Redis를 학습한 이후에 프로젝트에 적용하여 기존 메모리에 저장하고 있던 refresh token을 Redis에 저장하도록 변경했다. 기존 메모리에 저장하던 방식은 서버가 여러 대일 경우 한 곳의 서버에만 계속 요청을 보내야 한다는 단점을 Redis로 분리하여 서버가 여러 대일 때 어떤 서버에 요청하여도 제대로 된 응답을 내릴 수 있게 하였다. 또한 Redis는 일정 기간 이후에는 삭제할 수 있는 기능을 사용해서 간편하게 refresh token을 관리할 수 있게 되었다.

 

  • annotation을 이용한 로그인 인증 처리

interceptor만을 통해 로그인 인증 처리를 하고 있었다. 이렇게 하다 보니 회원가입을 진행하는 POST /api/members 는 로그인 인증 처리를 하면 안 되고 멤버 리스트 조회를 위한 GET /api/members 는 로그인 인증 처리를 해야 하는 상황이 생겼다. interceptor에서 request를 통해 /api/members 일 경우 HttpMethod가 GET이면 인터셉터 실행, POST면 인터셉터 실행하지 않는 등의 로직을 넣고 싶지 않아서 @LoginRequired 라는 커스텀 어노테이션을 정의하고 로그인이 필요한 요청인 메서드에만 @LoginRequired 어노테이션을 통해 로그인 인증 처리를 하도록 인터셉터를 수정했다.

 


💦 부족했던 것

  • CORS를 세팅한 이후에 문제없이 잘 동작하다가 3주 차 수요일에 preflight 요청에서 CORS 문제가 발생했는데 해결하지 못했다. OPTIONS 요청이 제대로 처리가 되지 않는 것 같은데 마지막까지 제대로 해결을 하지 못해 아쉽다.
  • 3주 차에 체력을 모두 소진한 상태라서 집중이 잘 되지 않았다. 체력관리를 한다고 했는데 실패했다.

06/27 ~ 07/03 기간 동안의 회고록이다.

 

한 일

  • 코드스쿼드 미션
  • 코드스쿼드 수료
  • 원티드 - Show Me The Code 코딩테스트
  • 프로그래머스 - Dev-Matching 코딩테스트

 

잘한 점

  • Redis를 공부한 이후에 미션에 적용한 것
  • annotation을 통해 로그인이 필요한 API를
  • 코딩테스트에 도전한 것

 

아쉬운 점

  • 체력을 모두 소진한 것
  • 3주차 문서화를 제대로 하지 못한 것
  • CORS 문제가 발생하였는데 해결하지 못한 것

 

다음 주 목표

  • 휴식
  • 1일 1 알고리즘

이번 주, 나의 생각

 

 마지막 프로젝트를 마무리하고 6개월 간 진행했던 코드스쿼드 과정을 수료했다. 이번 주 컨디션이 나빠서 마지막 프로젝트를 깔끔하게 마무리 하지 못한 것 같아서 아쉽다. 그래도 프로젝트 과정에서 새로운 기술들을 공부하고 적용하는 과정에서 많이 배울 수 있었다. 코드스쿼드 후기는 추후에 컨디션이 회복되면 작성을 하려고 한다.

 

프로젝트 후기

 

 토요일, 일요일에는 코딩테스트를 진행했는데 토요일 테스트는 한 개도 풀지 못했다. 이미 풀어봤던 문제와 비슷한 유형인데 비효율적인 내 풀이를 개선하지 않고 그냥 넘어갔던 것 때문에 이번 테스트에서는 시간초과로 솔브에 실패했다. 요즘 알고리즘을 풀고 나서 다른 사람의 풀이를 보고 더 효율적인 풀이가 무엇인지 다른 사람은 어떻게 접근했는지 확인하는 것을 게을리 한 결과인 것 같다. 다시 문제를 풀기만 하는 것이 아니라 다른 사람의 풀이도 확인하면서 공부해야겠다. 그래도 일요일에 진행한 코딩테스트에서는 모두 풀이에 성공했다. 하지만 아직 이력서를 준비하지 못해서 Dev-Matching에서 좋은 결과를 기대하기는 힘들 것 같다.

06/20 ~ 06/26 기간 동안의 회고록이다.

 

한 일

  • 코드스쿼드 미션

 

잘한 점

  • 미션을 진행하면서 백엔드 멤버뿐 아니라 클라이언트 분들과도 활발히 소통한 것

 

아쉬운 점

  • 쉬지 않고 계속 미션을 하다보니 체력적으로 한계가 온 것

 

다음 주 목표

  • 코드스쿼드 미션

 


이번 주, 나의 생각

 

 이번 주도 저번 주와 마찬가지로 코드스쿼드 미션에 거의 모든 시간을 보냈다. 프로젝트에서 GitHub OAuth와 JWT를 통해 로그인 기능을 구현하였고 이 과정에서 클라이언트 개발자와 많은 이야기를 나누었다. 프론트엔드분들과의 대화를 통해 내가 프론트엔드에 대해 지식이 전무해서 너무 서버 중심으로 로그인 기능을 구현하여 API를 만든 것을 인지하였고 API를 수정하여 로그인 기능을 마무리 지을 수 있었다. 마지막 프로젝트가 일주일 남았는데 끝까지 열심히해서 많이 배울 수 있었으면 좋겠다.

이번 프로젝트는 프론트엔드와 협업하고 있어서 CORS설정을 할 필요가 있었다. 간단하게 CORS에 대해 공부한 것과 spring에서 CORS 설정 하는 방법에 대해서 정리해보려고 한다.

 

CORS (Cross Origin Resource Sharing)

브라우저는 보안 상의 이유로 도메인 외부의 자원은 허용한 출처에 한해서만 자원을 사용할 수 있도록 하는 정책을 말한다.

 

SOP(Same Origin Policy)

동일한 출처에 대해서만 리소스를 공유하는 정책을 말한다.

 

- 일반적으로 출처란 Protocol + Host + port 를 의미하며 출처가 동일하다는 것은 Protocal, Host, port가 모두 같은 경우를 말한다. (브라우저별로 동일한 출처에 대한 기준이 다르지만 Internet Explorer를 제외하고는 거의 모든 브라우저가 Protocal, Host, port 가 같아야 동일 출처라고 판단한다.)

 

CORS 문제가 생기는 이유

HTTP 요청에 대해서 어떤 요청을 하느냐에 따라서 CORS또는 SOP를 따르는 다른 정책을 가지고 있다.

  • HTML → 기본적으로 CORS
  • XMLHttpRequest, Fetch API 등 → SOP
    • SOP정책을 사용하는 경우 서로 다른 도메인에 대한 요청을 보안상 제한하기 때문에 CORS를 위한 응답 헤더를 통해 다른 도메인에 대한 요청을 허용하도록 해야 함

 

CORS 동작 방식

  • Preflight Request  
    • 브라우저에서 서버로 예비 요청을 보낸다.
    • 예비 요청이 완료되면 본 요청을 서버로 보낸다.
    • 대부분의 경우 프리플라이트 방식을 사용한다.

Preflight Request

 

  • Simple Request 
    • 예비 요청 없이 본 요청을 바로 보내는 것
    • 본 요청만 보내기 위한 조건이 까다로움
      • 요청 메소드는 GET, HEAD, POST
      • Accept, Accept-Language, Content-Language, Conetent-Type, DPR, Downlink, Save-Data, Viewport-Width, Width 를 제외한 헤더 사용 금지
      • Content-Type은 application/x-www-form-urlencodedmultipart/form-datatext/plain 만 허용
    • 까다로운 조건, json 사용이 불가능한 단점이 있기 때문에 사용하기 쉽지 않음
    • ‼️Simple Request 조건이 만족하면 Simple Request로 동작하고 조건을 만족하지 못한다면 preflight Request 로 동작하게 된다.

Simple Request

 

  • Credentialed Request
    • 인증된 요청을 사용하는 방법으로 다른 출처 간 통신에서 보안을 강화하고 싶을 때 사용하는 방법
    • XMLHttpRequest, fetch API는 별도의 옵션 없이 브라우저의 쿠키 정보나 인증과 관련된 헤더 정보를 담지 않는다.
      • credentials 옵션을 통해 인증과 관련된 정보를 담을 수 있게 해줄 수 있음
        • same-origin (default) → 같은 출처 간 요청에만 인증 정보를 담게 함
        • include → 모든 요청에 인증 정보를 담게 함
        • omit → 모든 요청에 인증 정보를 담지 않게 함
    • credentials 에서 include option 사용 시 Access-Control-Allow-Origin 헤더에 * 사용 불가능
    • Access-Control-Allow-Credentials: true 헤더가 응답에 포함되어 있어야 함

 

 

Spring에서 CORS 해결 방법

1. WebMvcConfiguerer를 상속하여 addCorsMappings 메서드를 구현한 클래스를 Bean으로 등록

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:3000", "http://localhost:80")
                .allowedMethods("GET", "POST", "PATCH", "DELETE")
                .allowCredentials(true)
                .maxAge(3000);
}

2. Interceptor에서 Header 직접 추가 후 interceptor 등록

@Component
public class CorsInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, origin, content-type, accept");

        return true;
    }
}

 

 

 

참고 자료

https://evan-moon.github.io/2020/05/21/about-cors/

토이 프로젝트로 알고리즘 스터디에서 사용할 슬랙 봇 알람 봇을 만들며 템플릿 콜백 패턴을 적용했었다. 템플릿 콜백 패턴이 무엇인지, 적용한 이후 코드가 어떻게 개선 되었는지 기록해보려고 한다.

 

템플릿 콜백 패턴은 전략 패턴의 한 종류이므로 전략 패턴에 대한 사전지식이 있어야 한다.

 


전략 패턴

  • 런타임 시점에 동적으로 구현체를 선택할 수 있도록 하는 것
  • 구체적인 전략을 캡슐화하여 실행 시 전략을 바꿀 수 있도록 함

 

전략 패턴 구조

Context

  • 사용할 전략을 DI 받거나 setter 등을 통해 구체적인 전략을 주입받아 사용하는 곳
  • Strategy에게 메시지를 보냄

Strategy

  • 인터페이스 또는 추상 클래스로 전략(알고리즘)을 정의해놓음

Strategy 구현체

  • 실제 사용할 전략들로 전략(알고리즘)을 구현해놓음
  • Context에 DI 또는 setter로 주입되는 객체


템플릿 콜백 패턴

  • DI에서 사용하는 특별한 전략 패턴
  • 전략 패턴에 익명 내부 클래스를 가미해서 사용하는 방법
  • 주로 try/catch/finally 블록을 사용하는 코드에 사용

 

템플릿 콜백 패턴 구조

클라이언트 (템플릿을 사용하는 부분)

  • 콜백 오브젝트(익명 클래스)를 만들고 콜백이 참조할 정보를 파라미터로 제공

템플릿 (콜백 메소드을 사용하는 부분)

  • 정해진 흐름을 따라 작업, 콜백 오브젝트의 메소드 호출

콜백 (익명 클래스로 구현되어서 템플릿에서 사용되는 메소드)

  • 클라이언트에서 구현한 익명클래스로 동작

 

장점

  • 전략패턴은 사용하는 팩토리 객체가 필요하지만 템플릿 콜백 패턴은 팩토리 객체 없이 해당 객체를 사용하는 메소드에서 바로 익명클래스로 구현하여 전략을 선택 할 수 있음

단점

  • 인터페이스를 사용하지만, 실제 사용할 클래스를 직접 선언하기 때문에 결합도가 증가한다.템플릿 콜백 패턴은 전략 패턴의 한 종류이므로 전략 패턴에 대한 사전지식이 있어야 한다.

 


템플릿 콜백 패턴 적용 전과 후의 코드 비교

토이 프로젝트에 템플릿 콜백 패턴을 적용하게 된 배경

슬랙 봇 알림 프로젝트는 크게 3가지 기능이 있다.

  • 매주 월, 목 18:00에 문제 출제 요청하는 알람 발송 기능
    • 배포 후 crontab으로 주기적으로 알람 요청하는 파일 실행
    • s3 에 스터디원들 이미지 업로드 후 알람에 스터디원 이미지 포함

  • /money [이름] 입력 시 벌금 현황 조회 기능
    • 매주 목요일 18:00마다 웹페이지 크롤링해서 데이터 최신화
    • 커맨드 입력 시 입력한 값에 따른 데이터 조회 후 알맞은 메시지 발송

  • /recommend [level] 입력 시 알고리즘 문제 추천 기능
    • 백준, 프로그래머스 알고리즘 문제를 크롤링 후
    • 커맨드 입력 시 입력한 값에 따른 데이터 조회 후 알맞은 메시지 발송

 

위와 같은 기능을 만들면서 간단한 DB가 필요할 것 같아서 DBMS를 사용하기보다 파일DB를 사용하기로 했다. 파일 DB를 사용하다보니  try/catch/finally 블록이 포함된 파일 읽기, 파일 쓰기에 관련된 로직들이 많이 작성되었고 이에 따라 템플릿 콜백 패턴 적용이 가능해보여 적용하게 되었다.

 

 

파일 읽는 로직 템플릿 콜백 패턴 적용 전 후 비교

MyFileReader 인터페이스를 선언해서 파일 읽는 전략을 정의함

MyFileReader 인터페이스에서 공통적으로 사용되는 객체 생성하는 전략을 정의함

원래라면 바로 익명 클래스로 구현하는 것이 맞지만 익명클래스에서 중복되는 로직이 많아서 중복되는 로직을 담은 파일 읽는 전략을 구현한 클래스를 정의함

 

 

 

 

파일 쓰는 로직 템플릿 콜백 패턴 적용 전 후 비교

MyFileWriter인터페이스를 선언해서 파일 쓰는 전략을 정의함

익명클래스에서 중복되는 로직이 많아서 중복되는 로직을 담은 파일 쓰는 전략을 구현한 클래스를 정의함

 

 

코드에 템플릿 콜백 패턴을 적용함으로써 중복되는 try/catch/finally 블록을 효과적으로 추출할 수 있었고 파일 읽기 및 쓰기에 재사용할 수 있는 전략을 정의해둠으로써 이후에 프로젝트를 확장해나가면서 파일 I/O 작업이 있을 때 보다 쉽게 작업할 수 있을 것 같다.

+ Recent posts