항해 99 - 2023.03.01 TIL
주특기 주차 미니 프로젝트도 얼추 마무리 되어가고 있다.
협업 간에 예상치 못한 문제가 발생했던 것도 있고,
새로운 기술을 서버에 적용해보면서 부딪혔던 것들에 대해서 회고해보고자 한다.
그동안, Postman을 사용해서 프론트와의 협업없이 개발을 진행해왔는데, 이번에 협업을 진행하면서 Cors 에러라는 것에 대해서 알게 되었다.
Cors (Cross Origin Resource Sharing)
CORS는 W3C에서 서로 다른 Origin에서 자원(Resource)을 공유할 수 있도록 하기 위해 내놓은 정책을 말한다. 여기서 서로 다른 Origin이라는 것은 도메인 또는 포트가 다르다는 것을 의미하기 때문에 서로 다른 도메인 주소 사이에서 데이터(API 요청과 응답)를 주고받을 수 있도록 하기 위한 정책이라고 말할 수 있다.
기존 Same-Origin Policy(동일 출처 정책)은 다른 Origin(도메인/포트)에서의 자원 공유를 보안 이슈로 인하여 제한해왔다. 하지만 기술의 변화로 인해서, 클라이언트 단과 서버의 도메인과 포트를 따로 유지하는 상황이 발생했다. 또한, 외부 API를 연동할 때에도 App과 외부 API의 Origin이 다르다는 문제가 발생하게 되었다.
Cors를 활용해서 내가 허용하는 Origin에만 요청을 허용하도록 서버에서 설정해줄 수 있다.
방법은 두 가지가 있다.
1. 컨트롤러에서 설정
2. Spring Security에서 설정
우리 팀은 Spring Security 에서 적용을 시도했다.
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:3000");
config.addExposedHeader(JwtUtil.AUTHORIZATION_HEADER);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
config.validateAllowCredentials();
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
Spring Security 사용시 CORS에 걸리지 않으려면 Authentication Filter 인증 보다 앞단계의 필터/인터셉터에서 path 검증로직이 일어나야만 한다.
메서드에 설정할 값들을 세팅해주면 된다. 값을 전부 허용하는 것으로 " * "를 사용할 수 있다.단, 스프링 버전이 올라가면서 .allowedCredentials(true) 와 .allowedOrigins("*") 를 동시에 사용할 수 없도록 변경되었다고 한다.
이번에는 Cors 세팅에 참여하지 못했는데, 다음 프로젝트 때는 직접 담당해서 해보고 싶다.
소프트-딜리트
이번 프로젝트 때 soft-delete라는 것을 처음 알게 되었다. 삭제 처리를 했는데 DB에는 남겨둔다? 처음에는 왜 그게 필요한 건지 의문이 들었는데 매니저님의 설명을 듣고 필요성에 대해 깨닫게 되었다.
만약, 사용자가 악의적인 댓글을 달거나, DB에 무언가 증빙이 될 자료가 들어있다고 가정해보자.
사용자가 아이디를 삭제한다면, Hard-Delete 상태에서는 연결된 모든 게시물, 댓글 등의 데이터가 함께 삭제될 것이다.
이후에 데이터를 찾으려고 해도 찾을 수 없게 되는 것이다.
이것을 방지하기 위해서, 삭제 시에 flag를 변경하고 DB에는 남아있으나 조회되지 않는 상태로 유지할 수 있다.
Soft Delete (Logical Delete)
- DB에서 삭제를 할 때 실제 Row가 삭제 되지 않으며 flag를 통한 제어 방식
- 컬럼의 자료형은 boolean 혹은 datetime을 사용한다.
@Entity
@Table(name = "project")
@SQLDelete(sql = "UPDATE project SET deleted = true WHERE id = ?")
// delete 구문 입력 시 추가로 delete 컬럼이 true로 변경되도록 쿼리를 날린다.
@Where(clause = "deleted = false")
// isDeleted의 디폴트 값은 false이다.
public class Project extends Timestamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
....
private boolean deleted = Boolean.FALSE;
public void enableDeleted(){
this.deleted = true;
}
public void disableDeleted(){
this.deleted = false;
}
}
여러 가지 방법이 있지만, @SQLDelete 어노테이션을 사용해서 소프트 딜리트를 구현할 수 있다.
Delete 쿼리가 발생 시, UPDATE 쿼리를 함께 날려주어 삭제 상태에 해당하는 필드의 값을 변경해주면 된다.
@Where 어노테이션은 해당 엔티티 조회 시, where 조건을 추가할 수 있다. 위의 조건으로 설정하면 deleted = false인 값만 조회가 된다.
AWS RDS / S3 ... 그리고 CI / CD
RDS와 S3는 지난 포스트에서 다루었는데, RDS는 세팅 도중 다른 팀원이 세팅을 먼저 완료해서 S3로 넘어가게 되었다. S3는 세팅 후 파일 업로드, 삭제까지 다루어보았다.
대부분 보안/네트워크 설정이 어려웠는데, 구글링을 통하여 어떻게 진행하면 되는지 참고해가면서 세팅을 진행했다.
다음 번 프로젝트 때는 혼자서도 세팅해볼 수 있을 것 같다.
프로젝트를 진행하면서 서버 담당자가 기능이 추가되거나 애플리케이션이 수정될 때마다 계속해서 merge하고 ec2에 배포를 진행해야한다는 것을 알게 되었다. 피로도를 줄이기 위해서, CI/CD라는 개념을 알게 되었고 이것을 서버에 적용해보고자 했다.
CI (Continuous Integration)
지속적인 통합
Github 업로드 - Build - Test 하는 과정 중 Build / Test 과정을 자동화.
CD (Continuous Delivery, Deployment)
지속적인 제공 / 배포
Build 와 Test를 거친 후, 배포하는 과정을 자동화
GitHub Actions
자동화를 위해 사용하는 것이 GtiHub Actions 였다.
깃허브 레포지토리 내에 WorkFlow를 생성할 수 있는데, 어떤 상황이 발생했을 때 어떤 작업을 수행할 것인지 직접 스크립트로 작성할 수 있었다.
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run a one-line script
run: echo Hello, world!
- name: Run a multi-line script
run: |
echo Add other actions to build,
echo test, and deploy your project.
- Workflow는 다양한 Job으로 구성됨
- 여러 Job이 있을 경우, Default로 병렬 실행
- build라는 job을 생성하고, 그 아래에 2개의 step이 존재하는 구조
- runs-on은 어떤 OS에서 실행될지 지정
- strategy - matrix 인자를 사용하면 어떤 파이썬 버전에서 테스트할지 확인할 수 있음. 자세한 내용은 밑 예제를 통해 확인할 수 있음
- 위 예제에선 run할 때 env(환경 변수)를 설정하는 예제가 없는데, 밑에서 환경 변수를 등록하는 방법에 대해 안내할 예정
- steps의 uses는 어떤 액션을 사용할지 지정함. 이미 만들어진 액션을 사용할 때 지정
결과적으로는, CI / CD는 실패했다. CI / CD 구현을 위해서 Docker라는 오픈 소스 프로젝트를 사용하고자 했다.
아무런 사전 공부가 없는 채로 진행하다보니, Docker를 왜 사용하는지도 모르고 일단 시도해보았는데, WorkFlow를 작성하던 도중 기술매니저님께서 Docker를 사용할 이유가 없다는 것을 알려주셔서 진행을 포기하게되었다.
Docker를 사용하지 않고 CI/CD를 구현한다면 다음 번에 원활하게 구현해볼 수 있을 것 같다는 생각이 들어서 다음 프로젝트 때 서버를 담당해서 EC2/ RDS / S3 연동과 CI / CD 작업을 완성해보려고 한다.
느낀 점
프로젝트 진행 도중 백엔드 두 분이 개인사정 때문에 참여가 어렵게 되었고, 기존 인원에서 두 명이 줄어든 채로 작업을 진행했다. 다행히도, 나와 함께 백엔드를 담당하신 분의 실력이 뛰어나셔서 무사히 설계했던 대로 백엔드 구현을 할 수 있었다.
새로운 기술에 도전하고, 끝이 보이지 않는 오류를 경험하며 한숨쉬게 되지만, 정상적으로 실행이 되었을 때에 오는 희열은 이루말할 수 없는 것 같다. 이번에 적용해보지 못한 기술들을 다음 번 프로젝트 때는 꼭 완성해봐야겠다.