2021년 하반기 회고록(프로그래머스 백엔드 데브코스)
올해 2021년 하반기의 제 삶은 '데브 코스' 하나의 단어로 정의할 수 있을 것 같습니다.
그만큼 7월부터 시작해서 12월까지 5개월 동안 이 교육에 올인하며 지내왔습니다.
이제부터 데브 코스에 대해서 얘기를 진행해 나가겠습니다.
데브 코스
데브 코스는 프로그래머스에서 운영하고 있는 교육과정입니다. 백엔드뿐만 아니라 프론트엔드, 인공지능, 자율주행 등 여러 종류의 교육과정이 존재하고 있습니다.
저는 그중에서 백엔드 부분 교육과정에 지원하였습니다.
https://programmers.co.kr/learn/courses/12177
K-Digital Training: 클라우드 기반 백엔드 엔지니어링
× 다음 기수를 듣고 싶다면 대기자 신청을 해주세요. 가장 먼저 연락드리겠습니다. K-Digital Training100% 정부지원 프로그래머스 데브 코스 :클라우드 기반 백엔드 엔지니어링 백엔드 개발 배경지식
programmers.co.kr
이 교육에 지원한 이유는 모든 것이 마음에 들었지만, 그중에서도 커리큘럼이 정말 마음에 들었기 때문입니다.
백엔드 직무에 계속 지원해왔지만 경험과 지식이 매우 부족하다고 느꼈던 당시의 저에게 정말 필요했던 과목들이었습니다.
그래서 망설임 없이 지원했고, 코딩 테스트와 1번의 면접 과정을 거치고 합격해 수강하게 되었습니다.

온라인 강의
모든 강의를 오프라인이 아니라 온라인으로 수강했습니다.
자바 - DB - Spring - JPA - 클라우드 - 협업 - Spring Security
이러한 순서로 강의가 진행되었습니다.
첫 주는 자바를 써봤다는 자신감으로 '초반 강의는 별거 없겠지'라는 마음으로 강의를 듣게 되었는데 그것은 나의 큰 오만과 착각이었습니다.
자바를 계속 써온 것은 사실이지만 자바의 특징과 장점 등에 대해 전혀 고려하지 않으면서 코딩을 해왔기 때문에 점점 따라가기 벅찼습니다.
그렇게 허겁지겁 첫 주가 지나가고 DB는 이론 위주로 진행되었기 때문에 금방 지나갔습니다. (이때 자바 공부를 했어야 했는데....)
드디어 대망의 스프링.. 자바와 마찬가지로 과거 국비교육에서 스프링을 써봤기 때문에 조금만 들으면 금방 이해하면서 따라갈 수 있을 줄 알았지만 긴 공백기와 아무것도 모르고 사용했었던 탓인지 진짜 혼돈의 카오스였습니다😭
강사님은 모든 지식을 다 알려주는 스타일이 아니셨고, 그러다 보니 중간중간 스스로 찾아보고 해결해야 할 문제들이 많았습니다.
또한, 매주 과제도 주어졌기 때문에 부담은 계속 배가 되었고 결국 감당할 수 없는 경지에 이르렀습니다.
다시 생각해보니 공부를 먼저 하고 과제를 진행한다는 게 패착이었습니다. 실력이 부족해도 일단 부딪혔으면 어떻게든 됐을 텐데.. 교육기간 중 제일 아쉬운 선택이기도 했습니다.
이 기간 동안 추가로 자바와 데이터베이스 스터디도 진행하였는데, 역시 패기롭게 참여했지만 두 개 다 저에겐 수준이 높았던 스터디였습니다.
자바는 '실전 자바 소프트웨어 개발'이라는 책으로 진행했고, 데이터베이스는 'SQL 레벨업'이라는 책으로 진행했습니다.
자바 스터디는 외국책이라 그런지 번역이 매끄럽지 않았고, 아직 자바의 기초를 잡아가고 있던 시기에 더 깊은 지식이 들어오니 제가 많이 받아들이지 못했던 것 같습니다.
데이터베이스 스터디도 얇은 책에 생각보다 심도 있게 내용을 다루었기 때문에 이런 게 있구나~라고 받아들이면서 지나갔습니다.
그렇게 약 1달간의 스프링 강의를 듣고 간단한 클론 코딩을 진행한 후에 JPA를 수강하게 되었습니다.
JPA를 처음 접했을 때 저에게 굉장한 신선함을 받았습니다.
직접 쿼리를 짜면서 MyBatis만 사용해보았기 때문에 자동으로 테이블을 생성하고, 기본 CRUD를 제공하는 JPA는 정말.. 대단한 친구였습니다.
하지만, 객체 관점에서 엔티티를 설계하고 연관관계를 매핑하고 DTO도 고려하는 등 생소하고 헷갈리는 부분이 많았습니다.
그래서 홧김에 김영한 님 강의를 다 사버린 후 언제 듣지 언제 듣지 하다가 한 교육생분이 JPA스터디원을 모집해 참여하게 되었습니다.
동시에 2주간 클론 프로젝트도 진행했는데, 이 기간은 REST API와 JPA의 기초를 쌓을 수 있었던 소중한 시간이었습니다.
이 기간 동안만큼은 시간을 효율적으로 사용하고 싶어 계획을 세우고 그대로 하루를 보내려고 많은 노력을 했었습니다.

그렇게 마지막 1달 프로젝트를 앞두고 클라우드와 Spring Security에 대한 강의를 수강하였습니다.
클라우드는 처음 접하는 것이 워낙 많았기 때문에 호기심으로 재미있게 들을 수 있었지만, 시큐리티는... 원리를 이해하기 굉장히 어려웠습니다.
다양한 필터들의 존재와 각기 다른 역할들... 그리고 상황에 따라 추가해야 하는 코드들... 들으면 들을수록 미궁에 빠지는 기분이었습니다ㅠ
마지막 날까지 밤새 다른 강의도 찾아서 수강했지만 결국 이해하지 못한 채 마지막 프로젝트가 시작되었습니다.
프로젝트
기획
백엔드끼리만 진행하는 것이 아닌 처음 뵙는 프런트분들과 협업해야 하는 프로젝트였습니다.
각자 생각한 아이템을 기획서로 작성해 제출해 투표를 많이 받은 아이디어가 선정되는데 제가 제출한 아이디어는 탈락되어 투표했던 프로젝트 중 한 곳의 팀원으로 참여해야 했습니다.
다행히 제가 평소에 관심 있는 반려동물 주제의 프로젝트였고, 당시 같은 팀이었던 분도 계셨기 때문에 팀 구성은 굉장히 만족스러웠습니다.
프론트분들도 처음 대면했을 때 굉장히 열정이 가득하신 것을 보고 더 만족감이 높아졌습니다.
그렇게 프로젝트 시작하기 전 기획에 대해서 다듬는 시간을 가졌는데, 이때 프론트와 백엔드의 시각이 많이 다르다는 것을 느꼈습니다.
백엔드는 DB 설계 관점에서 바라본다면, 프론트는 UI, 컴포넌트 등 다양한 관점에서 바라봤기 때문에 생각이 다른 부분이 많았습니다.
그래서 1번 만나서 끝내는 게 아닌 2 ~ 3차례에 걸쳐서 같이 기획을 정리하는 시간을 가졌습니다. 이 시간 덕에 기획이 잘 잡혔던 것 같습니다.
설계
이번 프로젝트의 목표 중 하나가 프론트분들이 작업을 쉽게 할 수 있도록 도와주는 것이었기 때문에 같이 작성한 User Story를 기반으로 API 명세서를 빠르게 작성한 후 피드백을 요청했습니다.


그리고 이 설계를 기반으로 API를 Mocking 해 프론트에게 전달하려고 했습니다. 이 이유는 프론트 입장에서는 백엔드에서 넘어가는 데이터로 작업을 진행해야 하는데, 가 데이터라도 설계한 데이터가 넘어간다면 작업이 훨씬 쉬워질 거라고 생각했기 때문입니다.
번거로울 수 있지만 추후 발생할 병목 사항을 줄이기 위함이었고, 저희 입장에서도 API 틀을 잡을 수 있어서 투자한 시간이 아깝지 않았습니다.
세팅
추가로, 프로젝트 세팅에도 많은 시간을 들였는데 Github Repo, Jacoco, CheckStyle, SonaQube 등 코드 품질 향상과 PR정책을 정해 더욱 수준 높은 프로젝트를 진행하기 위해 노력했습니다.
아래는 프로젝트의 build.gradle인데 라인 수가 굉장히 긴 것을 확인할 수 있습니다.
buildscript { ext { queryDslVersion = "4.4.0" } } plugins { id 'org.springframework.boot' version '2.5.7' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'org.asciidoctor.jvm.convert' version '3.3.2' id 'java' id 'jacoco' id 'checkstyle' id 'org.ec4j.editorconfig' version '0.0.3' id "org.sonarqube" version "3.3" } group = 'com.pet' version = '0.0.1' sourceCompatibility = '14' configurations { asciidoctorExtensions compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } ext { set('snippetsDir', file("build/generated-snippets")) } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-aop' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'com.auth0:java-jwt:3.18.2' implementation 'org.mapstruct:mapstruct:1.4.2.Final' implementation 'org.apache.commons:commons-lang3:3.12.0' implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" implementation 'ca.pjer:logback-awslogs-appender:1.4.0' implementation 'com.github.maricn:logback-slack-appender:1.4.0' annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" annotationProcessor "org.mapstruct:mapstruct-processor:1.4.2.Final" annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' annotationProcessor( "javax.persistence:javax.persistence-api", "javax.annotation:javax.annotation-api", "com.querydsl:querydsl-apt:${queryDslVersion}:jpa") compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java' annotationProcessor 'org.projectlombok:lombok' asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' testImplementation 'org.springframework.security:spring-security-test' } sourceSets { main { java { srcDirs = ["$projectDir/src/main/java", "$projectDir/build/generated"] } } } sonarqube { properties { property "sonar.projectKey", "prgrms-web-devcourse_Team_i6_comepet_BE" property "sonar.organization", "prgrms-web-devcourse" property "sonar.host.url", "https://sonarcloud.io" property 'sonar.coverage.jacoco.xmlReportPaths', "$buildDir/jacoco.xml" } } editorconfig { excludes = ['build'] } compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' check.dependsOn editorconfigCheck test { outputs.dir snippetsDir useJUnitPlatform() finalizedBy 'jacocoTestReport' } asciidoctor { configurations 'asciidoctorExtensions' inputs.dir snippetsDir dependsOn test } bootJar { dependsOn asciidoctor from("${asciidoctor.outputDir}") { into "BOOT-INF/classes/static/docs" } } jacocoTestReport { reports { html.enabled true html.destination file("$buildDir/jacocoHtml") xml.enabled true xml.destination file("$buildDir/jacoco.xml") } finalizedBy 'jacocoTestCoverageVerification' } jacocoTestCoverageVerification { def Qdomains = [] for (qPattern in '*.QA'..'*.QZ') { // qPattern = '*.QA', '*.QB', ... '*.QZ' Qdomains.add(qPattern + '*') } violationRules { rule { enabled = true element = 'BUNDLE' limit { counter = 'LINE' value = 'COVEREDRATIO' minimum = 0.00 } excludes = [] + Qdomains } } } checkstyleMain.source = "src/main/java" checkstyle { maxWarnings = 0 configFile = file("${rootDir}/config/checkstyle/google-style.xml") configProperties = ["suppressionFile": "${rootDir}/config/checkstyle/google-style.xml`"] toolVersion = "8.24" }
세팅을 모두 마치고 CI/CD도 시작 전 미리 구축해놓는 것이 프로젝트에 도움이 되기 때문에 진행하기로 하였습니다.

위와 같이 Github Action에서 문제없이 빌드되면 S3에 jar파일을 올리고 Code Deploy로 EC2에 전달하는 방법이었습니다. 그리고 단계별로 Slack 알림을 받을 수 있도록 해놓아 동료들이 어떤 작업을 하고 있는지 실시간으로 파악하기 쉬웠습니다.
script를 작성하는 과정에서 제가 오타를 많이 내어 시간이 많이 지체되었던 해프닝도 있었습니다....ㅠ
개발
그렇게 모든 작업을 끝내고 각자 맡은 업무 개발을 시작했습니다.
저는 사이트에 들어가자마자 바로 보이는 실종/보호 게시글에 관련된 모든 API와 이미지 업로드를 담당했습니다.
먼저 이미지 업로드를 개발해 놓아야 팀원들이 사용할 수 있었기 때문에 AWS S3를 이용해 이미지 업로드를 구현했습니다.
S3 생성 시 설정을 하나만 다르게 해도 오류가 발생하는데 이유를 찾기 힘들고, 환경변수 설정도 생각보다 까다로워 시간을 많이 지체했지만 팀원 케빈의 도움으로 빠르게 해결할 수 있었습니다.
DTO와 Entity 간 변환해주는 컨버터를 직접 구현하지 않고 MapStruct라는 라이브러리를 사용하기로 하였는데 진행도 상 제가 제일 먼저 사용하게 되어 사용법을 익히게 되었습니다.
그러던 와중, 많은 블로그를 찾아보고 사용했는데 제 의도대로 잘 되지 않아 멘토님께 사용법을 여쭈어 보았는데 구현체를 잘 살펴보니 이미 잘 되고 있던 것을 제가 제대로 안 보고 착각하고 있던 것이었습니다... 뻘쭘~
그렇게 라이브러리도 도입해 팀원분들에게 사용법을 공유하자, 팀원들이 다양한 방법으로 잘 사용하셔서 저도 배우면서 사용할 수 있었습니다.
그리고 대망의 API 개발을 시작하는데 저는 개발해야 하는 API가 10개 이하였음에도 불구하고 시간이 오래 걸렸습니다.
그 이유는 게시물에 연관되어 있는 테이블이 약 8개 정도 되었고, 등록 API를 시작했는데 Service단에서 신경 써야 될 부분이 너무 많았습니다.
그래서 제 부분뿐만 아니라 다른 팀원분들의 구역도 기웃기웃하면서 개발을 진행했고, 그러다 보니 점점 코드의 양이 방대해져 3~4일에 걸쳐서 하나의 API를 만들었습니다.
이후부터는 수월하게 진행했습니다. 물론 중간중간 어려움이 없던 건 아니지만 등록 API에 비하면... 나머지는 순조로웠습니다.
라고 생각했지만 게시물을 수정하는 API를 얕보았던 게 문제였습니다. 등록과 똑같이 하면 되겠지 라는 생각이었는데 이미지와 태그 관련해서 신경 써야 될 부분이 추가로 있었습니다.
도저히 기존 방향에서는 방법이 생각나지 않아 보내고 받아오는 json 필드를 약간 수정해 API를 완성했습니다.
이처럼 혼자 끙끙대는 와중에 팀원들은 각자 맡은 역할을 착착 해내고 있었고, 새로운 기술들도 많이 도입해 무한한 리스펙을 보냈습니다.
마지막까지 밤을 새우면서 기존 코드를 약간 다듬고, 더 효율적인 코드로 개선하는 등의 노력을 하고 프론트에서 요청하는 오류 부분을 수정하면서 바쁘게 지냈습니다.
마감기한을 맞출 수 있을지 굉장히 걱정했는데 어찌어찌 피피티와 발표 영상을 만들어 제출했습니다. 진짜 하얗게 불태웠습니다...
결과
워낙 다른 팀에 잘하시는 분들이 많아서 기대를 하지 않았지만, 프로젝트 투표 결과 1등을 차지했습니다!!
Github Repository
https://github.com/prgrms-web-devcourse/Team_i6_comepet_BE
GitHub - prgrms-web-devcourse/Team_i6_comepet_BE: [3조] 반려동물 실종 & 입양 서비스 🐶
[3조] 반려동물 실종 & 입양 서비스 🐶. Contribute to prgrms-web-devcourse/Team_i6_comepet_BE development by creating an account on GitHub.
github.com
진짜 고생했던 하루하루가 스쳐 지나가고 고생했던 팀원분들을 생각하며 살짝 눈물이 날 뻔했지만 꾹 참았습니다ㅎㅎ
데브 코스가 끝난 지 1주가 지난 현재 몸상태를 회복하며 슬렁슬렁하고 있는데 프로젝트 때를 생각하며 다시 마음을 다잡을 때가 온 것 같습니다..
느낀 점
데브 코스는 정말 올 한 해 중 가장 기억에 남고 뜻깊은 하반기였습니다.
방황하던 저에게 한줄기 빛이었고, 기대 이상으로 동료분들을 포함해 멘토분들 매니저분들.. 얻어가는 부분이 굉장히 많았습니다.
아직도 끝난 게 실감이 안 난 만큼 저에게 매우 큰 비중을 차지하고 있었다는 것을 깨닫고 있습니다.
이제 이력서를 작성하고... 코딩 테스트와 면접 준비를 하고... 취업을 하는 일만 남았습니다.
물론 원하는 기업에 들어가기까지 쉬운 과정은 없겠지만 견뎌야만 하나니...
드디어 내일이 새해인데 내년에는 꼭 제가 원하는 것을 다 이루는 한 해가 되었으면 좋겠습니다.
그럼 여기서 하반기 회고를 마치도록 하겠습니다ㅎㅎ
힘들었지만 즐거웠다 2021년~!~!