본문 바로가기

(179)

스프링부트 버전업 이슈 LocalTime 값을 DB에서 조회시 Invalid value for NanoOfSecond 오류 분석

현상 스프링부트 버전업 이후 LocalTime 필드를 가지는 Entity를 조회할 때, 아래와 같이 LocalTime의 nano second에 음수 값이 할당되어 에러가 발생했음.PostgreSQL DB에 저장된 TIME 타입 컬럼을 java의 LocalTime 변환 시에 아래와 같은 오류 발생java.time.DateTimeException: Invalid value for NanoOfSecond (valid values 0 - 999999999): -843000000LocalTime 변환 시, 나노초 값으로 음수가 유입되면서 발생원인변경된 스프링부트 버전업에 대응되는 하이버네이트 버전에서 변경이 있었고 이 부분에 버그가 존재했음.스프링 부트 버전: 2.6.4 ➡️ 3.2.4하이버네이트 버전: 5.6...

2024 인프콘 후기

2022년부터 꾸준히 참가신청을 했으나 항상 당첨이 안됐었고... 설마 3연속 탈락이겠어~라는 생각과함께 2024 인프콘도 티켓팅 대열에 합류했다. 하지만 결과는...이렇게 올해도 인프콘 다녀온 사람들의 후기글을 보며 부러워하는 일만 남았다고 생각하고 포기하고있던 찰나! 혹시라도 전산 착오라는 이야기가 나올까봐 잽싸게 초대권을 등록하고 인프콘 행사날만 기다렸다. 2024 인프콘의 발표 세션들은 2024 인프콘 발표/프로그램 세션에서 확인할 수 있는데 몸이 5개면 좋을련만...닝겐의 한계로 각 세션별로 한 개씩밖에 듣지 못했고 아래와 같이 총 5개의 세션을 들었다. 지속 성장 가능한 설계를 만들어가는 방법 - 김재민님디버깅 마인드셋: 디버깅의 고통을 절반으로 줄이는 고수들의 행동패턴 따라하기 - 배휘동님처..

JDK 21, Virtual Thread

Virtual Thread란…? JDK 21에 새롭게 들어온 개념 (2023.09.19 에 LTS 출시) gradle 8.4v 부터 지원 kotlin v1.9.20 부터 21 바이트 코드 지원 Spring 6.1, Spring boot 3.2 부터 지원 Jetbrain Intellij 2023.3 JDK 21(LTS)에 추가된 경량 스레드, OS 스레드를 그대로 사용하지 않고 JVM 내부 스케줄링을 통해서 수십만 ~ 수백만개의 스레드를 동시에 사용할 수 있게한다. 전통적인 Java의 Thread Java의 Thread는 OS Thread를 랩핑한 것 (Platform Thread) Java 애플리케이션에서 Thread를 사용하면 실제로 OS Thread를 사용한 것 OS Thread는 생성 갯수가 제한적..

[서평] - Go 언어로 배우는 웹 애플리케이션 개발

Go에 대해서 아예 무지한 상태로 해당 책을 접하게되었다. 평소 다른 언어를 찍먹해보고 싶은 욕구가 가득하던 찰나에 서평의 기회가 찾아와서 책을 간단히 리뷰해본다. 항상 어떤 새로운 언어를 배울 때는 그 언어가 어떤 목적을 가지고 탄생했는지가 늘 궁금해진다. 나뿐만 아니라 다른 사람들도 똑같지 않을까하는 생각에 1장만 간단하게 요약해려고한다. 😃 Go가 탄생한 이유 Go의 탄생이유는 Go at Google에서 확인할 수 있고 개발 배경은 'Why did you create a new language?'에 있다. 2007년 구글이 개발한 프로그래밍 언어인 Go는 당시 구글이 겪던 아래의 문제들을 해결하기위해 탄생했다. 수십 분, 수 시간이 걸리는 빌드 동일한 내용의 표현 방법이 프로그래머마다 달라서 생기는..

2023년을 회고하며...

2023년의 시작은 너무 슬프고 한편으로는 회사라는 존재에 대해서 많은 생각을 하게했던 사건을 겪게된 시기이다. 회사는 그대로이다. 하지만 큰 변화가 있었는데 그 변화는 함께 회사의 성장을 위해 함께 치열하게 고군분투했던 많은 동료들을 떠나보내게된 것이다. 경제를 잘 알지는 못하지만 2022년 하반기부터 닥쳐온 세계적인 경제위기로 투자시장이 얼어붙게되고 스타트업이었던 우리 회사도 그 영향 탓에 자금 조달을 하지 못했다. 꾸준히 적자를 내왔던 우리 회사는 자연스럽게 인건비라는 고정비를 줄이는 선택을 하게되었다. 아무런 언질도 없이 갑작스럽게 진행된 구조조정은 떠나는 구성원과 남게된 구성원 모두를 혼란스럽게했다. 1) 살아남았다. 결과적으로 나는 운이 좋게도 회사에 남을 수 있게 되었다. 이제 막 2년차로..

Springboot 3 환경에서 Feign Client 에러

물리치료 스터디에 도움이될만한 사이드 프로젝트를 진행하고있다. 프로젝트는 SpringBoot 3.x + Kotlin 1.8.22 버전을 사용하고있다. 해당 프로젝트의 MVP는 프론트 혹은 앱과 같은 클라이언트의 리소스를 들이지 않고, 오로지 Slack API와 통신하며 쓸 수 있는 애플리케이션을 만드는 것이다. 슬랙 API(= 외부 API)를 사용해야했는데, 이런 Http 클라이언트를 무엇을 쓸지 고민해보다가, Netflix의 Feign 클라이언트가 사용성이 좋았던 기억이 있어서, Feign 클라이언트로 셋티을 했다. 그런데 셋팅을 분명 잘한 것 같은데, 아래와 같은 에러가 계속해서 떳다. Consider defining a bean of type 'org.springframework.cloud.open..

각종 오류 및 해결 방법

스프링부트 버전업 이슈 LocalTime 값을 DB에서 조회시 Invalid value for NanoOfSecond 오류 분석

728x90

현상 

스프링부트 버전업 이후 LocalTime 필드를 가지는 Entity를 조회할 때, 아래와 같이 LocalTime의 nano second에 음수 값이 할당되어 에러가 발생했음.

PostgreSQL DB에 저장된 TIME 타입 컬럼을 java의 LocalTime 변환 시에 아래와 같은 오류 발생

java.time.DateTimeException: Invalid value for NanoOfSecond (valid values 0 - 999999999): -843000000

LocalTime 변환 시, 나노초 값으로 음수가 유입되면서 발생

원인

변경된 스프링부트 버전업에 대응되는 하이버네이트 버전에서 변경이 있었고 이 부분에 버그가 존재했음.

  • 스프링 부트 버전: 2.6.4 ➡️ 3.2.4
  • 하이버네이트 버전: 5.6.5 ➡️ 6.5.5

하이버네이트 버전이 변경되면서 DB에 LocalTime 저장 로직이 변경됨.

  • LocalTime → Time 객체로 변환하는 로직이 변경됨 
  • 5.6.5 버전에서는 ms 를 버린채(=0 고정) Time 객체를 생성했으나 
  • 6.5.5 버전에서는 LocalTime의 nano second에서 ms (소숫점 3자리)를 추출하여 Time 객체에 할당해줌. 즉 ms가 0이 아님
  • DB에 저장할 때는 ms값을 잘 변환하여 실제 ms와 동일한 양수의 값으로 잘 저장된다.

DB에서 시간값을 조회한 후 Time을 LocalTime으로 변환할 때 버그가 존재함

  • DB에서 시간 값을 읽어서 Time 객체를 생성하는 과정은 이상이 없음 
  • Time 객체를 LocalTime 객체로 변환할 때, Time 객체로부터 ms를 계산하여 초기화하고있는데 time 객체의 getTime 메서드 값이 음수가 나오는 경우가 있음 
    • ms 계산 로직: time.getTime() % 1000 
  • LocalTime은 날짜 정보(년,월,일)를 가지지 않아서 default 날짜(유닉스 시간(Unix time)인 1970년 1월 1일 00:00:00 UTC에서 년원일)가 임의로 지정됨. 
    • 1970년 1월 1일 00:00:00일 때 time.getTime() = 0 
  • KST 타임존인 경우에는 UTC의 기준을 맞추기 위해서 9시간을 빼주게 되고 이때, 0시~9시 시간일 경우 time.getTime()은 음수가 되고 ms값도 음수가 됨

재현 조건

  1. 타임존 설정
    • KST TimeZone인 경우에 DateTimeException 에러 발생
    • UTC TimeZone인 경우에는 에러 발생 X
  2. 특정 시간대
    • 0 ~ 8시: DateTimeException 에러 발생 
    • 9 ~ 23시: 에러 발생 X
  3. 나노초 여부
    • 나노초 O: DateTimeException 에러 발생 
    • 나노초 X: 에러 발생 X 
  4. 나노초의 소수점 자릿수
    • 소수점 3자리 존재: DateTimeException 에러 발생
    • 소수점 3자리 이후(4~9자리) 존재: 에러 발생 X (소수점 4번째 자리가 5이상인 경우 반올림되어서 예외 발생)

원인 분석 상세 

  • LocalTime 객체는 원래부터 ms를 가지고있다. 
  • 1970년 이전은 timestamp가 음수 
  • Localtime 은 날짜가 없기 떄문에 timestamp계산을 위해 1970으로 날짜 셋팅 
  • Long type의 timestamp는 zone이 없기때문에 UTC TimeZone을기본으로 사용 
  • 우리시스템은 서울이므로 -9시간을 해줌. 1970/1/1 00:09:00 - 9시간 -> 음수발생. 하지만 ms를 사용하지 않아서 마지막은 000으로 끝나서 그동안 이슈없었음 우리에러는 timestamp에서 ms를 추출하기위해 timestamp % 1,000을 사용하는데 이게 음수여서 발생

✅ Application 에서 (PostgreSQL) DB로 시간을 저장하는 과정

LocalTime 타입을 DB에 저장할 때는 TimeJdbcType.getBinder 메서드를 호출하게된다. 이 내부에서 TemporalJavaType.unwrap 메서드를 호출하여 Time 객체로 변환한다. … ➡️ TimeJdbcType.doBind ➡️ LocalTimeJavaType.unwrap ➡️ new Time(time.getTime() + DateTimeUtils.roundToPrecision( value.getNano(), 3 ) / 1000000)

HikariProxyPreparedStatement.setTime ➡️ PreparedStatementSpy.setTime(index, time)

DB에 저장할 떄는 RdbmsSpecifics.formatParameterObject 메서드를 호출하여 (DML 쿼리를 위한) 문자열로 변경하여 new SimpleDateFormat(dateFormat).format(object) 를 호출하여  MM/dd/yyyy HH:mm:ss.SSS 문자열 포맷으로 파싱하여 저장한다. 이때 object로 들어가는 값은 LocalTimeJavaType.unwrap으로 생성한 Time 이다. 

실제로 저장하려고했던 LocalTime은 08:32:00.943476 으로 ms 까지 잘 변환된 것으로 보인다. (4번째 자리 반올림)

❌ DB에서 Application으로 시간값을 조회하는 과정

DB에 저장된 데이터를 읽어오는 경우 
TimeJdbcType.doExtract ➡️ LocalTimeJavaType.wrap 호출하여  LocalTime 으로 변환하게 된다.

Time 객체는 unwrap했을 때(Application → DB)와 동일한 값의 Time 객체를 생성했다. 하지만 ms를 구하는 로직에서 문제가있어 음수의 값이 도출되게 된다.

LocalTime은 날짜 정보(년,월,일)를 가지지 않지만 Time 계산을 위해서는 날짜 정보가 필요하다. 따라서 default 날짜(epochTime or 유닉스 시간(Unix time)인 1970년 1월 1일 00:00:00 UTC에서 년원일)을 임의로 지정해준다. 여기에서 KST 타임존인 경우에는 UTC의 기준을 맞추기 위해서 9시간을 빼주게되고 이때, epochTime은 음수가 나오게된다.   (위에서 언급한 유닉스 시간의 경우 0) LocalTime을 Time으로 변환하여 DB에 저장할 때는 문제가 없지만 Time을 LocalTime으로 변환하는 과정에서는 0 ~  UTC와 KST의 시간차이 - 1 시간인 경우에는 fastTime(Time 객체의 상태값) 이 음수가되는데 ms를 이 값에서 1000을 나눈 나머지로 계산하기 때문에 음수 ms가 발생하게된다.

참고로 해당 버그 fix된 것으로 보이지만 아직 release가 안된 상태이다. 
LocalTime 객체를 저장하거나 조회할 때는 nano seconds를 제거(or 0으로 초기화)하는 Converter를 구현하여 설정해주어서 저장 혹은 조회 시, nano second가 저장되지 않도록 임시 방편을 취했고 추후 버그 fix 버전으로 release되면 hibernate 버전 업을 진행하려고한다. 

728x90
세미나

2024 인프콘 후기

728x90

2022년부터 꾸준히 참가신청을 했으나 항상 당첨이 안됐었고... 설마 3연속 탈락이겠어~라는 생각과함께 2024 인프콘도 티켓팅 대열에 합류했다. 

하지만 결과는...

이렇게 올해도 인프콘 다녀온 사람들의 후기글을 보며 부러워하는 일만 남았다고 생각하고 포기하고있던 찰나! 

이 누추한 곳에 귀한분이 자미님... 너무 감사합니다. 🥲

혹시라도 전산 착오라는 이야기가 나올까봐 잽싸게 초대권을 등록하고 인프콘 행사날만 기다렸다. 

현장의 열기... 아마 세션 시작 직전이라 많은 사람들이 발표장으로 들어갔을 타이밍에도 불구하고 사람이 많다...

2024 인프콘의 발표 세션들은 2024 인프콘 발표/프로그램 세션에서 확인할 수 있는데 몸이 5개면 좋을련만...
닝겐의 한계로 각 세션별로 한 개씩밖에 듣지 못했고 아래와 같이 총 5개의 세션을 들었다. 

  • 지속 성장 가능한 설계를 만들어가는 방법 - 김재민님
  • 디버깅 마인드셋: 디버깅의 고통을 절반으로 줄이는 고수들의 행동패턴 따라하기 - 배휘동님
  • 처음으로 기술 리더가 된 개발자를위한 안내서 - 박서진님
  • 클린 스프링, 스프링 개발자를 위한 클린코드 전략 - 이일민(토비)님
  • 객체지향은 여전히 유용한가? - 조영호님
  • OpenAPI Generator 실전편: 효율적인 코드를 작성하는 법 - 이한님

 

1. 지속 성장 가능한 설계를 만들어가는 방법 - 김재민님

설계를 잘하는 방법은 "설계를 하지 않는 것!" 이다. 라며 시작부터 강한 어그로(?)를 끄셨던 재민님은 개념과 격벽의 이야기를 시작으로 웹툰 서비스, 커머스 등등의 사례로 예시를 들며 발표를 진행해주셨다. 

개념: 웹툰 서비스를 만든다고 생각해보자.
작가, 연재, 작품, 결제, 구매, 리뷰.... 더 나아가서는 PD, 랭킹, 포인트, 소장, 만료 같은 개념들을 떠올려볼 수 있고 자연스럽게 이를 그룹핑하는 시도를 하게될 것이다. 개념을 생각하면 할 수록 더 많은 개념그룹이 생기게된다.

격벽: 격벽은 접근에 대한 제어와 통제의 역할

개념과 격벽이 잘 설정되었는지에대한 척도

격벽을 만들었다면 변경한 개념과 관련된 코드만 수정이 생긴다. 
하지만 개념과 격벽을 잘못 관리하고있다면 전혀 다른 개념 그룹이 수정된다거나 혹은 같은 개념 그룹이 수정되지 않는다거나하는 상황이 발생한다. 

예시) 대출 - 사례

신청, 실행, 상환을 대출의 행위 정도로 바라봤으나 대출을 수정했을 때, 신청 서비스, 실행 서비스, 상환 서비스가 대출의 수정에 모두 영향을 받았고 대출이라는 개념을 너무 크게 잡은 것이 아닌가하는 의심이 발생 ➡️  행위로 분리했던 신청, 실행, 상환을 행위가 아니라 대출과 같은 크기의 개념으로 뽑아냄 상환이 실패하면 상환 재시도, 추가 이자 등을 실행하게되는데, 이 개념으로 넘어갈 때 까지 상환 개념에서는 상황 성공 혹은 실패 상태가 나왔을 때, 개념적으로 자기의 역할을 다 한 것! 상환 실패에 대한 재시도 연체라는 새로운 개념을 만들어냄. 상환 실패의 결과물로 연체를 만들어 냄. 상환 재시도, 추가 이자 --> 연체 납부와 연체 이자 (행위) 상태는 개념이 아니다. 개념이 상태에 표기되는 것

핵심 포인트

하나의 개념이 많이 쓰이면 분리를 검토하자
상태에 의해 개념이 생기면 격벽을 세워보자.

 

개념도 계층이 나뉘게된다

1 급 개념 > 2급 개념 > 3급 개념 ... > N급 개념

개념이 많은 소프트웨어는 개념 계층을 나눠서 생각해야한다.
비즈니스를 지탱하는 것을 개념으로 지칭하는 것이 맞다.
외부연동사는 개념없는 곳에 속할 확률이 높다.   (외부연동사는 커머스 사례에서 언급된 케이스)

격벽보다 더 높은 수준 참조 불가의 벽 의 안쪽에 외부 연동사를 둬야한다.

외부 연동사는 (수집 및 정제 영역으로 보고) batch를 통해서 db에 데이터를 긁어 저장하는 용도 정도로만 써야한다.
참조 불가의 벽은 별도의 모듈로 분리해서 확실하게 격벽을 쳐주는 것이 좋다. core 모듈에 외부에 의존하는 개념이 생기면 안된다.
core 모듈에 외부에 의존하는 개념이 생기면 안된다. 

미묘하지만 더 중요 혹은 덜 중요한 개념이 있다.
모든 것이 개념이 되진 않기 때문에 핵심 개념이 되는 것을 찾아서 관리해줘야하고 올바르게 개념이있는지 지속적으로 체크해줘야한다
외부나 핵심 비즈니스를 관리해서 더 명확하게해야한다.

진짜 핵심 

  • 인정하자 
    • 요구사항은 계속 변한다.  (요구사항은 변한다는 사실만 별하지 않는다) 
    • 완벽할 설계란 없다. 
    • Software는 Soft 해야한다. 
  • 하지말자 
    • 요구사항이 완벽해야 설계 가능해요. 
    • 우리 설계에서 그건 개발 못해요. (변경된 요구사항을 수용할 수 있도록 설계르 변경해야한다) 
    • 설계해 봐야 개발 일정이 나옵니다. 
  • 상기하자 
    • 성급한 설계는 모든 것을 망가트린다 
    • 과도한 설계는 모든 것을 망가트린다 
    • 설계는 필요한 만큼만 하자

 

분석 > 설계 > 구현   하지말고 지금할 수 있는 최소한의 구현을 먼저 했으면 좋겠다.
개념 + 격벽을 활용한 구현을 진행하고 증명 + 피드백 하면서  << 테스트 코드로 반복하면서... => (자연스럽게) 설계가 나오게 된다.

전달하고싶은 이야기

개념을 잡고 >> 격벽을 세워 >> 구현을 채워나가 >> "설계를 완성"한다


 

2. 디버깅 마인드셋: 디버깅의 고통을 절반으로 줄이는 고수들의 행동패턴 따라하기  - 배휘동님

디버깅 역량을 효과적으로 키우기 어려운 이유 

  1. 신호 인식
  2. 과거 경험과의 유사성
  3. 전제조건
  4. 사전지식
  5. 과거 경험과의 유사성 

디버깅 고수들의 머릿속을 파헤치다 

Q. 디버깅을 잘하는 사람과 못 하는 사람의 차이가 큰 게 무엇인지 ? 

디버깅은 마법이 아닌 마술이다. (본인도 어떻게 하는지 모르는) 
  • 마법: 신비하고 이해하기 어려운 무언가 
  • 마술: 트릭, 손기술을 익히면 누구나 배울 수 있는 기술 

디버깅의 3단계 

  • 원인 파악 
  • 문제 해결 
  • 사후 처리 

디버깅 고수들은 원인 파악에 시간을 많이 쏟는다.  (전문성의 핵심) 

(디버깅 정의) 
디버깅 = 의도대로 동작하지 않는 무언가를 정상화하는 행위 
정상적인 상황이 무엇인지 명확히 정의를 내려야한다. 
정보 수집, 가설 설정, 가설 검증 <=> 어떤 조건에서 어떤 순서로 어떤 일들이 벌어져야 하는가?   => 심적 표상 

문제 원인 파악을 위한 5단계 가이드 (이자 훈련볍) 

  1. 문제 정의 
  2. 정상 동작 정의 
  3. 최소 재현 환경 구축하며 관찰 
  4. 차이를 발생시키는 다양한 원인 탐색 
  5. 가설 검증 및 검증 

위의 단계는  심적 표상을 만들어내는 것 

문제 원인 파악을 위한 5단계 가이드 (들어가기 전에) 

(사전) 작업을 위한 계획 세우기 (시간 분배 등) <-- 적절하게 멈추고 회고하기 

  1. 문제 정의 
  2. 정상 동작 정의 
  3. 최소 재현 환경 구축하며 관찰 
  4. 차이를 발생시키는 다양한 원인 탐색 
  5. 가설 검증 및 검증 
    • (사후)  해결법 설계, ROI 파악, 우선순위 결정... 

고수들은 1~5 단계를 꼭 순서대로 진행하는 것은 아님. 
1 <-> 5단계를 왔다갔다하며 진행함 

1. 문제 정의 

  1. 이정표 만들기
  2. 사전에 작업 계획 세울 때 함께 할 때도 많음
  3. 중간회고할 때마다 메타인지를 켜주는
  4. 다른 사람에게 문제에 대해 설명하고
  5. 이게 없으면 당장 중요하지 않은 문제에 빠져들어 시간을 허비할 수있음.

2. 정상 동작 정의

  • 심적 표상 만들기. '정상적인 환경에서는 어떤 조건에서, 어떤 순서로 어떤 일들이 벌어져야 하는가?'
  • 현재 벌어지는 일을 관찰 + 관찰한 정보 및 이미 알고있는 정보 적어보기
  • "올바른 동작"을 테스트코드처럼 적어보기 given when then
  • 올바른 동작을 정의하지 못하겠다면, 그걸 정의하기 위한 추가 정보를 여러 소스에서 수집해야 함.

3. 최소 재현 환경 구축하며 관찰

  • 직접 재현하기. '문제가 있는 부분을 어떻게 핀포인트하여 격리시킬까?'
  • 문제가 발생했던 사용자의 환경과 동작을 그대로 따라함 (세션 리플레이)
  • 격리하며 패턴 관찰 (조건을 무조건 참으로 만들거나, 정상이 될 때까지 하나씩 지우거나, 빈 프로젝트에서 시작해서 비정상이 될 때까지 나아가거나...)
  • 내 환경에서 재현이 안 된다면, 재현 조건 파악을 위한 추가 정보를 여러 소스에서 수집해야 함. (로그 심기, 사용자 인터뷰)

4. 차이를 발생시키는 다양한 원인 탐색

  • 머릿속 지도 펼치기 '두 환경의 차이가 어디에서 왔을까?'
  • 추상적이든 구체적이든 떠오르는대로 적어보기
  • 도메인 경험이 많을수록 첫번째 옵션이 진실일 가능성이 높으나, 주니어는 훈련을 위해서라도 3개 정도는 적어보는 게 좋음
  • 다양한 옵션을 적기 어렵다면 추가 정보를 여러 소스에서 수집해야 함.

5. 가설 설정 및 검증 옵션을 검증

  • 가능한 가설 형태로 문장화
  • 실제로 작은 변경을 가하면서 가설대로 현상이 변하는지 관찰
  • 가설이 틀렸다면 (오히려 좋아) 무엇 때문인지 적어보고 다음 가설로 넘어감
  • 끝내 원인 파악이 안된다면, 원인 파악을 위한 추가 정보를 여러 소스에서 수집해야 함.

디버깅 고수들의 도구

  • 좋은 도구의 존재를 앎
  • 가장 중요한 실용적 도구: 상황에 맞는 디버거 (프론트의 경우 크롬 디버거)
  • 마인드셋 그 자체

디버깅 고수들의 습관

  1. TDD Toilet Driven Development <<< 화장실에서 생각할 시간을 강제로 만듦
  2. DDD (PR) Description Driven Development 의미단위로 커밋을 쪼개고, PR 제목, 내용도 잘 쓰고 (추후 검색할 수 있게) 커밋은 나중에 찾아보기 위함이다. PR은 보는 사람을 잘 이해하게
  3. IDD Issue Driven Development

3. 처음으로 기술 리더가 된 개발자를위한 안내서 - 박서진님

기술 리더가 어려운 이유 

컴퓨터<< 같은 입력, 같은 출력   정량적 지표는 승리 (실행 시간, FPS, ...)
사람<< 같은 행동, 다른 결과  정량적 지표는 패배 (PR 숫자, 근무시간, ...)

그럼에도, 모든 것의 성공률을 높이는 방법

"동료들이 당신을 좋아하나요?" 

신뢰의 차이

 Q. 이런 피드백이 들어왔는데, 이렇게 해보는 것은 어떠세요? 

신뢰관계 형성X: 아, 좋은 말씀 감사합니다...
신뢰관계 형성O: 아, 좋은 말씀 감사합니다! 해볼게요! 

팀 리더의 의사결정을 바로 수용 
솔직한 이야기를 털어놓음
인정과 피드백

이분을 어떻게 변화시킬까요? 
이분의 퍼포먼스를 어떻게 높일 수 있을까요? 

`팀원들은 나를 믿고 좋아하는가?`

팀원이 나를 신뢰하는 7가지 순간

유능, 소통, 예측, 관심, 안전, 유사, 

1. 유능함 (Capability): 얼마나 유능한가?

팀 리더가 개인 기여

Q. 리더는 역량적으로 모든 면에서 유능해야 하나요? 
역량 그 자체보다는, 팀원의 문제를 해결해주는 것이 중요하다. 

(성공한 스타트업은 진통제성 제품을 만드는 경우가 많다 -> 진통제성 문제는 풀기만해도 사람이 몰려든다)

팀원이 제일 고민하거나 귀찮아하는 일을 해결해주자 

> 팀원 사이의 갈등을 중재해서 해결, 
> 미팅이 너무 많아서 힘들어하는 팀원의 일정 조정 
> 팀 안에서 라포를 형성할 기회 형성
> 프론트엔드-백엔드 개발자 사이의 협의 조율 

2. 팀원에 대한 관심(= Benevolent Concern)

Q. 케미가 잘 맞는 팀원이 있고, 그렇지 않은 팀원이 있는데, 모든 팀원에게 다 개인적으로 관심을 가져야하나요?
Q. 팀원의 문제에 공감이 잘 안되는데, 어디까지 공감을해야 할까요?

말하기 방식에 초점을 맞출 필요가있다. 

동감이 아닌 공감 

* 동감: 상대방과 똑같이 감정 느끼기
* 공감: 상대방의 감정을 이해해주기  (T인 개발자여도 곰감은 배울 수 있다)

팀원의 고민에 대한 의식적인 관심 

1 on 1이나 커피챗에서 팀원이 고민을 
1주 뒤나 다음에 마주쳤을 때, 꼭 언급해서 
신경쓰고 있다는 '티'를 내면 신뢰의 수준이 바뀐다. 


(관심 받기를 싫어하는 사람은 없다)

3. 소통의 원활함 (Level of Communication)

양쪽이 번갈아가면서 이야기하고 있는가? 
양쪽이 말하는 길이가 비슷한가? 

티키타카가 잘되는가...? 

4. 리더와 팀원의 유사성 (Number of Similarities)

리더와 팀원이 **유사한 점을 공유**할수록 

리더와 적절한 TMI는 **대화의 마중물** 
팀원이 리더 앞에서 자신의 시시콜콜한 이야기를 먼저 꺼내기는 어렵다.

5. 심리적 안정감 (Security)

팀원이 현재 상황을 안전하다고 느낄수록 신뢰가 생긴다.

Q. 팀 리더로서 어려운 이야기를 피해야 하나요? 
Q. 팀원이 잘 할 수 있을지 걱정되는데, 팀원의 업무에 어디까지 간섭해야 하나요? 

안전하게 이야기할 수 있고, 안전하게 간섭할 수 있다고 생각함. 

  • 지배: `팀원의 경험이 부족한 경우` , 구체적인 지시 --> 지금 기어를 D로 바꾸세요.
  • 리드: `팀원의 경험이 있는 경우`, 덜 구체적인 지시 --> 고속도로를 탑시다.
  • 위임: `결과가 예측 가능한 경우` 아주 추상적인 지시 --> 잘 다녀오세요.

토스에서는 이 3가지 중 어떤 방법을 써서 지시를 내릴 것인지 공유하려고 노력하는 중.
진실된 칭찬으로 팀원의 심리적 안정감을 높일 수 있다.  (진실된이 중요하다)
모든 팀원에게는 칭찬할 부분이 있다. (팀원의 단점은 또한 장점이 된다)

신중한 팀원  <-> 우유부단한 팀원
행동이 빠른 팀원 <-> 성급한 팀원 
사교적인 팀원 <-> 말이 많은 팀원
진중한 팀원 <-> 말이 없는 팀원 

6. 예측 가능성(Predictability & Integrity)

리더의 말과 행동이 일치하고, 
팀원이 리더의 행동을 예측할 수 있을 때 신뢰가 증가한다. 


1. 상호가 동의할 수 있는 사실부터 시작
2. 기대수준에 대한 명확한 정의
3. 상호 소통

팀원의 성공이 리더의 성공이고, 
리더의 성공이 팀원의 성공일 때 신뢰가 증가한다.

7. 팀원과 리더의 목표를 일치시키기 (Alignment of Interests)

나의 목표를 팀원에게 설득하는 것이 아닌 
나와 팀원의 목표를 아우르는 **공동 목표의 설정**

4. 클린 스프링, 스프링 개발자를 위한 클린코드 전략 - 토비님

동욱님 왈

일반적인 클린 코드 인식: 구현 능력과 속도가 떨어진다 ?

클린 코드를 처음 이야기한 사람: 켄트 벡

켄트벡 왈

작동하는 깔끔한 코드(= Clean Code That Works) 론 제프리즈의 핵심을 찌르는 이 한마디가 바로 TDD의 궁극적인 목표

Clean Code That Works

작동하지 않는 클린코드 ?

  • 전 클린 코드를 추구해서 주석은 작성하지 않습니다
  • 코드가 클린하면 리팩터링할 이유가 없지요
  • 클린 코드는 버그가 적어서 테스트 코드가 없어도 되지 않나요?
  • 클린 코드를 작성해야 해서 일정을 지킬 수 없습니다.
  • 클린코드 원칙에 위배되어서 리뷰를 승인할 수 없습니다.

➡️ 코드 결벽증, 원칙 지향 개발 ?

클린 코드가 강조하는 것

  • 읽기 좋은 코드
  • 이해하기 좋은 코드
  • 확장하기 좋은 코드
  • 유지보수하기 좋은 코드 << 위의 3가지 말을 포함하는 말

클린코드가 강조하는 것은 유지보수성(maintainability)

만들면서 배우는 클린 아키텍처

  • 품질에 관한 요구사항(비 기능적 요구사항)에서 유지보수성은 특별하다
  • 유지보수하기 좋은 코드는 확장하기 좋고, 안전하고, 신뢰할 수 있고, 좋은 성능으로 발전시킬 수 있고, 상호 호환성이 뛰어나서 변경하기 쉽다.
  • 유지보수성은 코드의 변경가능성과 동의어

유지보수하기 좋은 클린 코드는

개발 생산성과 유지보수성은

유지보수성 <-- 코드 --> 생산성

유지 보수성과 개발 생산성은 서로 영향을 주는 관계이다.

부채(Dept)

기술 부채(= Technical Dept) - Ward Chunningham

  • 개발하는 소프트웨어에 대한 현재 이해를 반영하는 코드를 작성하고 빠르게 출시
  • 소프트웨어에 대해서 배우게 된 것을 리팩터링을 통해서 코드에 반영
  • 그렇지 않으면 이자가 쌓여서 점점 큰 부담

부채를 상환하는 행위를 리팩터링이라고 함.
(이 내용도 포함된다...)

  • 코드를 대충 작성하라는 게 아님
  • 부채는 나쁜 코드에 대한 변명이 될 수 없음
  • 처음부터 리팩터링하기 좋은 코드를 만들어야 함

부채가 지속적으로 효과를 발휘하려면 **리팩터링(부채상환)**이 동력이 되어야 한다

클린 코드 선순환

`유지보수성`이 좋은 코드는 변경가능성이 좋다
빠르게 변경되는 코드는 개발 생산성이 좋다
(이미 조영호님이 이에 같은 이야기를 하신적이 있음)

시작은 어떻게 할까...?

개발 시작은 빠르고 간단하게

  • 익숙한 기술로
  • 핵심 기능이
  • 동작하는
  • 가장 단순한 코드를
  • 리팩터링하기 좋게 작성

일부 기능을 완벽하게 만드는 것으로 시작 X

유지보수성과 생산성의 균형을 잡아줄 리팩터링을 잘 하려면?

테스트

그런데 테스트를 작성하면 구현 능력 구현 속도가 또...

테스트를 빠르고 효과적으로 작성하면서 개발하는 능력이 필요
테스트는 연습이 필요하다.

개발 시작은 빠르고 간단하게 ... <<

누구에게?

  • 삼체 문제(Three Body Problem): 세 개의 천체가 서로의 중력에 의해 영향을 받으며 움직이는 운동을 예측하는 문제
    • 세 개의 천체 문제는 비선형적이고 일반적인 해를 가지지 않아 예측이 매우 어렵다
    • 각 천체는 다른 두 천체의 중력의 영향을 미쳐 복잡하고 예측 불가능한 운동을 한다.

서로 영향을 주는 것이 세 가지만 있어도..

팀워크(teamwork)

코드 >> 유지보수성, 생산성, 팀워크 (풀기 어려운 문제...)

유지보수성, 생산성, 팀워크 이 3가지는 삼체 문제의 삼체와 같다.
하지만 이것을 가능하게 해주는 것은 '클린 코드'이다.

클린 코드의 많은 원칙은 팀의 동료 개발자와 관계를 두고 말한다.

나만을 위한 클린 코드는 없다

클린 코드의 많은 원칙은 상황에 따라

탐험(Exploration)

팀과 함께 결정하고 탐험하고 학습하고 성장한다 <-- 토비님 마인드

함께 탐험하는 것을 즐거워하는 팀

어떤 외국의 여성 개발자의 발표에서..

Great teams make great people.

친절

교양있는 개발자

교양: 자신의 말을 하더라도 다른 사람의 기분을 나쁘지 않게 하는 것
-> 쉽지는 않다. 교양이 저절로 생기는 것은 아니다.

함께 코드를 작성하고, 읽고, 변경할 동료 개발자들에게 친절한 코드 를 만들었으면 좋곘다.

[토비님의 당부]

항상 친절하세요.
동료에게
자신에게

 

(스프링 개발에도) 클린 코드의 선순환 구조, 삼체 균형 모두 동일하게 적용 가능

자기 책임에 충실한 오브젝트로 구분하고 의존관계를 유연하게
클린 (자동) 구성 정보

헥사고날 아키텍처

계층과 모듈 경계에는 API, 즉 인터페이스를 사용

테스트를 안 만들거면 스프링을 뭐하러?
스프링 가치의 절반은 테스트.. << 토비님의 의견

DB 테스트에는 @Transactional을 사용 (재민님, 동욱님은 반대의 의견을 내셨었고 토비님은 이에대한 생각을 SNS에 밝히신적이 있다)

클린 스프링에서는 트랜잭셔널을 사용해야한다. (ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 강하게 어필하심)

클린 코드는 항상 코드에 관심을 가지고 보살피는 사람이 작성한 것처럼 보인다.


5. 객체지향은 여전히 유용한가? - 조영호

(조영호님 발표 때는 세션 막바지에 가까워져서 체력이 바닥난터라 살짝 지쳐있어 누락된 내용이 많다 🥲)

Q. 객체지향은 여전히 유용한가요?
A. 왜 그런 생각을 하셨어요 ?

객체지향을 배울 필요는 있다. (객체지향 언어를 쓰기 때문에)
맡으시는 도메인 혹은 팀의 특성에 따라서 정말 배워야하는가? 가 궁금했을 것으로 추측

절차적인 설계 vs 객체지향 설계

가장 큰 포인트는 변경이다.

절차적인 설계

데이터와 프로세스를 별도의 클래스로 구현하는 방식

데이터를 바꾸면 프로세스도 같이 바껴야한다. (= 결합도가 높다)

객체지향 설계

캡슐화 때문에 데이터가 변경되도 하나의 클래스만 변경

  • 데이터가 바뀔 때는 객체지향 설계가 더 좋다.

객체지향 설계에서는 적합한 클래스로 분배되고, 분산된다.
객체지향 설계는 유연성을 위해서 큰 리팩터링이 필요하다. 그에반해 절차지향 설계는 국소적 리팩터링이 필요하다.

다른 코드를 수정하지 않고 데이터 변경과 타입 확장 가능
반면에 절차적인 코드는 무조건 내부 코드를 수정해야한다.

설계가 계속 발전하고 진행할 수록 코드의 규모만 커지지 일관성을 유지할 수 있다.
절차지향 설계는 그런 룰을 강제하기가 어렵다.

다형성과 기능 추가 사이의 긴장

타입 계층 전체 수정

절차적인 설계가 객체지향 설계보다 더 낫다

  • 절차적인 설계
    • 포맷 변경을 위한 데이터 변환
    • 데이터 중심
    • 데이터 노출
    • 기능 추가에 유리
  • 객체지향 설계
    • 규칙에 기반한 상태 변경
    • 행동 중심
    • 데이터 캡슐화
    • 타입 확장에 유리

내가 다루는 것이 데이터라면 절차적인 설계로 짜는 것이 좋다.

데이터의 응집도 단위로 클래스를 분리한 것이 아니라 행위 단위로 클래스를 분리했기 때문에

레이어 아키텍처

  • 프리젠테이션 레이어: 절차적, 데이터 변환
  • 서비스 레이어: 절차적, 애플리케이션 플로우 처리 (말 그대로 절차 이다!)
  • 도메인 레이어: 객체지향, 규칙 기반의 상태 변경
  • 퍼시스턴스 레이어: 절차적, 데이터 변환

특정한 목표에 맞게 절차적, 객체지향적 설계를 혼합해서 쓰고있다!

절차적인 데이터 조회

도메인 레이어가 필요없을 수 있음.

(질문을 이렇게 바꾸는게 더 적절하다! )
객체지향은 여전히 유용한가요? --> 객체지향은 언제 유용한가요? (혹은 언제 유용하지 않은가요?)

a를 배우는게 좋을까요? b를 배우는게 좋을까요? 등에 대한 질문...
일단 배워봐라

도구하나 배우라고 생각하는게 좋다.

어떤 설계도 어떤 기법도 모든 경우를 커버하지 못한다.

코드의 목적과 변경의 방향성에 따라
언제 어떤 기술을 사용할지 결정하세요

유용하지 않은 기술은 없다.
언제 어떤 경우에 유용한지를 생각하지 않는 경우가 많은 것 같음.

2024 인프콘이 끝난지 아직 하루도 안지났는데 벌써부터 2025 인프콘이 기대되는 것은 오늘 너무나도 끝내주는 발표들을 만난 탓이 아닐까 싶다. 즐거웠고 다른 세션들은 영상나오면 하나씩 챙겨볼 예정이다. 

728x90
프로그래밍 공부/Java

JDK 21, Virtual Thread

728x90

Virtual Thread란…?

  • JDK 21에 새롭게 들어온 개념 (2023.09.19 에 LTS 출시)
    • gradle 8.4v 부터 지원
    • kotlin v1.9.20 부터 21 바이트 코드 지원
    • Spring 6.1, Spring boot 3.2 부터 지원
    • Jetbrain Intellij 2023.3

JDK 21(LTS)에 추가된 경량 스레드, OS 스레드를 그대로 사용하지 않고 JVM 내부 스케줄링을 통해서 수십만 ~ 수백만개의 스레드를 동시에 사용할 수 있게한다.

전통적인 Java의 Thread

  • Java의 Thread는 OS Thread를 랩핑한 것 (Platform Thread)
  • Java 애플리케이션에서 Thread를 사용하면 실제로 OS Thread를 사용한 것
  • OS Thread는 생성 갯수가 제한적이고 생성, 유지하는 비용이 비쌈
  • 이 때문에 애플리케이션에서는 플랫폼 스레드를 효율적으로 사용하기 위해 Thread Pool을 사용함
 

위와 같은 동작 매커니즘에의한 Throughput(처리량) 의 한계

  • 기본적인 Web Request 처리 방식은 Thread Per Request(하나의 요청/하나의 스레드)
  • 처리량을 높이려면 스레드 증가 필요, But 스레드는 한정적이다. (OS 스레드 제약)

특히나 Blocking I/O 쪽에서 많은 문제가 발생함.

  • Thread 에서 I/O 작업을 처리할 때, Blocking이 일어난다.
  • 작업을 처리하는 시간보다 대기하는 시간이 길다.
 

그래서 우리는 Reactive Programming을 사용했었음.

  • Webflux 스레드를 대기하지 않고 다른 작업 처리 가능 (장점)
  • 코드를 작성하고 이해하는 비용이 높다 (단점)
  • Reactive하게 동작하는 라이브러리 지원을 필요로 한다 (단점)
  • JPA를 사용할 수 없고 R2DBC라는 래퍼런스가 상대적으로 적은 라이브러리를 사용해야 함 (단점)
 

그렇다면 왜 이렇게 구성이 되어있을까…?

Java Design

  • 자바의 디자인은 ‘스레드 중심’으로 구성되어있다.
  • Exception Stack trace, Debugger, Profiling 모두 스레드 기반
  • Reactive할 때, 작업이 여러 스레드를 거쳐 처리되는데, 컨택스트 확인이 어려워 디버깅이 어려움.

Virtual Thread가 해결하고자하는 문제

  • 애플리케이션의 높은 처리량 확보
    • Blocking 발생시 내부 스케줄링을 통해 다른 작업을 처리 (= 기존 자바의 한계)
  • 자바 플랫폼의 디자인과 조화를 이루는 코드 생성
    • 기존 스레드 구조 그대로 사용(= Webflux가 못한부분)
 

결론은 Virtual Thread는 Reacitve와 MVC의 장점만 차용한 케이스!

 
 

Virtual Thread가 앞에 따로 존재함.
뒤에 Fork/Join Pool이 Carrier Thread(Platform Thread와 거의 동일한 형태)
Carrier Thread는 OS Thread와 1:1 매핑되는 구조이긴 하지만 실제 Application에서는 Platform Thread가 아니라 Virtual Thread만 사용하게된다.

 
  • Virtual Thread가 Blocking되면 Virtual Thread와 Carrier Thread에서 Unmount 된다.
  • 그리고 다른 Virtual Thread가 해당 Carrier Thread와 Mount 된다.
  • OS Thread가 갯수에 제한이 있는 것에 비해서 Virtual Thread는 엄청난게 많이 생성할 수 있다.
 

사용법 예시

// Virtual Thread 방법 1
Thread.startVirtualThread(() -> {
    System.out.println("Hello Virtual Thread");
});

// Virtual Thread 방법 2
Runnable runnable = () -> System.out.println("Hi Virtual Thread");
Thread virtualThread1 = Thread.ofVirutal().start(runnable);

// Virtual Thread 이름 지정 
Thread.Builder builder = Thread.ofVirtual().name("JVM-Thread");
Thread virtualThread2 = builder.start(runnable);

// 스레드가 Virtual Thread인지 확인하여 출력
System.out.println("Thread is Virtual? " + virtualThread2.isVirtual());

// ExecutorService 사용 
try(final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 3; i++) {
        executorService.submit(runnable);
    }
}

Spring Boot(MVC) 적용법 (3.2 이상)

# application.yaml

spring:
  threads:
    virtual:
      enabled: true

Spring Boot(MVC) 적용법 (3.x)

// Web Request를 처리하는 Tomcat이 Virtual Thread를 사용하여 유입된 요청을 처리하도록 한다. 
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
    }
}

// Async Task에 Virtual Thread 사용
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor() {
    return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}

유의사항

유의사항 1

  • Platform Thread => Virtual Thread (X)
    • 전통적으로 사용하던 이 개념으로 Virtual Thread를 쓰게되면 성능 향상을 체감할 수 없을 것이다.
  • Task => Virtual Thread (O)

리소스라고 생각하기보다는 Task별로 Virtual Thread 할당

유의사항 2

Thread Local 사용시 주의

  • Platform Thread Pool을 사용할 때, 공유를 위해 ThreadLocal을 사용하던 관습
  • Virtual Thread는 Heap을 사용하기 때문에 이를 남발하면 메모리 사용이 늘어남.
    • Platform Thread와는 달리 Virtual Thread는 수십 수백만개까지 생성될 수 있기 떄문에 메모리 점유량이 확 늘어날 수 있어 메모리 이슈 발생 가능성이 있음.

유의사항 3

synchronized 사용시 주의

synchronized 사용시 Virtual Thread에 연결된 Carrier Thread가 Blocking 될 수 있으니 주의
(이런 경우를 pinning 이라고 함)

// synchronized 사용 (pinning 발생)
// 순차적 접근을 보장한다. 
public synchronized String accessResource() {
    return access();
}

// ReentrantLock 사용 (pinning 발생하지 않음)
private static final ReentrantLock LOCK = new ReentrantLock();

public String accessResource() {
    // 순차적 접근을 보장한다.
    LOCK.lock();
    try {
        return access();
    } finally {
        LOCK.unlock();
    }
}

I/O와 관련된 부분에 있어서는 Virtual Thread를 적용했을 때, 상당히 큰 효과를 누릴 수 있다.

적합한 사용처

  • I/O Blocking이 발생하는 경우 Virtual Thread가 적합
  • CPU Intensive 작업에는 적합하지 않음
    • ex) 이미지 프로세싱을해서 썸네일을 만든다. 동영상 인코딩을 한다거나. (I/O Intensive 작업이 아니라 CPU Intensive 작업이기 때문에 Virutal Thread를 도입한다고해서 크게 달라질게 없다)
  • Spring MVC 기반 Web API 제공시 편리하게 사용할 수 있다.
    • 높은 Throughput을 위해서 Webflux를 고려중이라면 대안이 될 수 있다.
    • Webflux 도입에도 (1)Stream 형태의 서비스를 제공해야하는 경우 vs (2)단순히 많은 처리를 하고 싶어서 가 있을 수 있는데, 이 중 (2)의 케이스는 Virtual Thread라는 새로운 대안으로 고민해볼 수 있다.

Virtual Thread에 대한 오해

  • Virtual Thread는 기존(Platform) Thread를 대체하는 것이 목적이 아니다.
  • Virtual Thread는 기다림에 대한 개선, 그리고 플랫폼 디자인과의 조화
  • 도입한다고 무조건 처리량이 높아지지 않는다.
  • Virtual Thread는 그 자체로 Java의 동시성을 완전히 개선했다고 보기는 어렵다.

Virtual Thread의 제약

  • Thread Pool에 적합하지 않다. Task 별로 Virtual Thread를 할당해야 함
  • Thread Local 사용시 메모리 사용이 늘어날 수 있음
  • synchronized 사용시 주의가 필요함. (carrier thread가 blocking 될 수 있음)
    • ReentrantLock을 사용
  • 제한된 리소스의 경우 semaphore를 사용

참고 자료

  • https://www.youtube.com/watch?v=vQP6Rs-ywlQ
  • https://techblog.woowahan.com/15398/
728x90
개발서적

[서평] - Go 언어로 배우는 웹 애플리케이션 개발

728x90

Go에 대해서 아예 무지한 상태로 해당 책을 접하게되었다. 
평소 다른 언어를 찍먹해보고 싶은 욕구가 가득하던 찰나에 서평의 기회가 찾아와서 책을 간단히 리뷰해본다.

항상 어떤 새로운 언어를 배울 때는 그 언어가 어떤 목적을 가지고 탄생했는지가 늘 궁금해진다. 
나뿐만 아니라 다른 사람들도 똑같지 않을까하는 생각에 1장만 간단하게 요약해려고한다. 😃

Go가 탄생한 이유 

Go의 탄생이유는 Go at Google에서 확인할 수 있고 개발 배경은 'Why did you create a new language?'에 있다. 

2007년 구글이 개발한 프로그래밍 언어인 Go는 당시 구글이 겪던 아래의 문제들을 해결하기위해 탄생했다. 

  • 수십 분, 수 시간이 걸리는 빌드
  • 동일한 내용의 표현 방법이 프로그래머마다 달라서 생기는 가독성 저하
  • 자동화 툴 작성이 어려움
  • 높은 비용이 드는 버전 관리 및 버전 변경
  • 멀티 코어 프로세서, 네트워크 시스템, 대규모 계산 클러스터 및 웹 프로그래밍 모델에서 개발할 때 발생하는 문제

위의 문제를 해결하기위해 Go는 아래와 같은 목표를 가지고 개발됐다. 

  • 동적 타이핑(dynamic typing) 언어의 특징인 쉬운 프로그래밍 
  • 정적 타이핑(static typing) 언어가 가진 효율성과 타입 안정성
  • 네트워크 프로그래밍, 멀티 코어 프로그래밍을 간단하게 구현해주는 병렬 처리 작성법
  • 대규모 시스템 및 대규모 개발 팀에 필요한 효율적인 프로그래밍
  • 간단한 언어 기능

Go 언어의 중요한 특징은 학술적인 목적으로 개발된 언어가 아니라 대규모 팀 개발에 발생하는 문제를 해결하고자 만들어졌다. 
이는 객체지향이나 함수형 프로그래밍의 기초가 되는 이론이나 수학적 접근법은 Go에서는 표현하기 어렵다는 의미이기도 하다. 

Go는 시스템 개발 중에 발생하는 문제를 해결하기 위해 탄생한 언어이기 때문에 개발자들의 요구에 따라 설계 이념을 지키는 범위 내에서 기능을 확장하는 경우도 있다. 

Go를 작성할 때는 먼저 Effective GoGo Code Review Comments 등의 코딩 가이드라인을 따라해보는 것을 권장한다.

장표를 가볍게 살펴본다. 


목차

  • Chap1. Go 언어 알아보기 
  • Chap2. context 패키지
  • Chap3. database/sql 패키지
  • Chap4. 가시성과 Go
  • Chap5. Go Modules 
  • Chap6. Go와 객체지향 프로그래밍
  • Chap7. 인터페이스 
  • Chap8. 오류 처리
  • Chap9. 익명 함수 및 클로저 
  • Chap10. 환경 변수 적용 방법
  • Chap11. Go와 의존성 주입
  • Chap12. 미들웨어 패턴
  • Chap13. 실습 내용에 대해
  • Chap14. HTTP 서버 만들기
  • Chap15. 개발 환경 정비하기
  • Chap16. HTTP 서버를 약한 결합 구성으로 변경하기
  • Chap17. 엔드포인트 추가하기
  • Chap18. RDBMS를 사용한 데이터베이스 처리 구현하기 
  • Chap19. 기능별로 HTTP 핸들러 구현 분할하기
  • Chap20. 레디스와 JWT를 사용한 인증 및 권한

장표들을 쭉 훑어보면 대략적으로 이 책이 어떤 정보들을 제공하는 것인지 알 수 있을 것이다.
Go 언어의 문법이나 기초를 알려주는 책은 아니기 때문에 Go를 완전 처음 접해보는 분이라면 적절한 수준의 책은 아닐거라고 생각되지만 
Go의 기본 문법을 다뤄봤고 웹 애플리케이션을 만들어봤다면 궁금해할만한 내용들을 Go 언어로 어떻게 구현하는지를 알려주는 책이다. 

Go 언어를 생전 처음 보는거다보니 이 책을 읽으면서도 잘 이해하지 못한 부분들이 많았고 웹 애플리케이션을 개발할 때, 필요한 여러가지 처리들이 있는데 각 처리를 어떻게해야하지? 하고 고민할 때, Go는 이렇게 구현하는구나? 와 같은 방향 가이드를 알려주는 책이다.
Go에서는 이런 느낌 식으로 이런 처리를 하는구나?, 이런 기능도 제공하는구나? 와 같이 흥미롭게 책을 읽었다. 

또한 Go는 객체지향 프로그래밍이 가능한지에 대한 Chap6의 내용도 흥미로웠다. (YES이면서 NO이기도 하다는 애매한 공식문서의 답변) 객체지향의 대표적인 특징인 캡슐화, 다형성, 상속 중 다형성을 지원하지 않기 때문이다. (일부만 지원)

Go에서는 상속을 표현하기위해서  embedded 를 사용하는 방식을 소개하기도하지만 이는 완전히 상속을 대체할 수 없다.
embedded의 개념을 딱 보면서 상속보다는 조합(?)의 개념이 아닐까 싶었는데 책에서도 역시나 이 개념은 composition(= 조합), mixin 과 같은 개념에 더 가깝다고 동일한 이야기를 하고있다. (괜히 뿌듯 😙)

이 외에도 에러 처리는 어떻게 할 것인지 DIP(= 의존성 역전 원칙), 환경 변수 적용 방법, 미들웨어 패턴 등등 혼자서 맨땅에 헤딩하며 공부하기에는 많은 시간과 노력이 소모될 수 있는 내용들에 대해서 전반적인 가이드를 해주고있다. 이 책에 나온 내용들을 숙지하면 모르는 내용을 찾기위해서 구글링하는 시간을 절약하는데 큰 도움이 될 것 같다. 

Go 문법에 대해서 어느정도 숙지되어있고 웹 애플리케이션을 개발에 도전하고있는 분이라면 이 책을 읽음으로인해 많은 시간을 절약할 수 있을 것으로 기대된다.

이런 독자들에게 이 책을 추천한다.

  • Go 문법에 익숙하고 Go로 웹 애플리케이션 개발에 도전하려는 사람
  • 이미 Go로 웹 애플리케이션을 개발하는 중이고 웹 애플리케이션에서 필요한 여러 처리들을 Go에서는 어떻게 해야하는지 잘 모르는 사람
728x90
회고

2023년을 회고하며...

728x90

2023년의 시작은 너무 슬프고 한편으로는 회사라는 존재에 대해서 많은 생각을 하게했던 사건을 겪게된 시기이다.
회사는 그대로이다. 하지만 큰 변화가 있었는데 그 변화는 함께 회사의 성장을 위해 함께 치열하게 고군분투했던 많은 동료들을 떠나보내게된 것이다. 

 

경제를 잘 알지는 못하지만 2022년 하반기부터 닥쳐온 세계적인 경제위기로 투자시장이 얼어붙게되고 스타트업이었던 우리 회사도 그 영향 탓에 자금 조달을 하지 못했다. 꾸준히 적자를 내왔던 우리 회사는 자연스럽게 인건비라는 고정비를 줄이는 선택을 하게되었다.
아무런 언질도 없이 갑작스럽게 진행된 구조조정은 떠나는 구성원과 남게된 구성원 모두를 혼란스럽게했다. 

1) 살아남았다.

결과적으로 나는 운이 좋게도 회사에 남을 수 있게 되었다. 
이제 막 2년차로 넘어가고 아직 개발 시장에서 경쟁력이 많이 부족한 나로써는 갑작스럽게 이직을 준비해야하는 상황을 겪지 않은 것이 한편으로는 다행이었지만 냉정하게 생각해봤을 때, 떠나는 사람들이 더 능력있는분들인데 내가아닌 다른 동료들이 나가야한다는 사실에 죄책감도 들었고 또 이런 상황을 만든 경영진에 화가 많이 났었다.

 

우리의 제품으로 고객에게 더 나은 서비스를 제공하고 더 나아가 고객가치 실현을 위해 애썼던 1년간의 노력이 물거품이되는 것만 같았다. 
대체할 수 없는 인력들을 갑작스럽게 내보내고 남은 사람들끼리 잘 해나갈 수 있을지 걱정이됐었다. 
이와는 별개로 설계를 잘하고 코드를 잘 짜는 코드도 중요하긴하지만 '돈 버는 코드가 짱이다'라는 생각을 가지게됐다. 

 

이렇게 불안한 와중에도 나에게는 두 가지 든든한(?) 포인트가 있었는데, 
첫 번째는 내가 이 회사를 오게된 큰 계기였던 씨툐(=CTO)님, 두 번째는 거의 1년 가까이 호흡을 맞추며 크고 작은 일들을 함께 이루었던 패스파인더 파트가 건재하다는 사실이었다. 마음 한켠에 이 두 가지가 사라지면 진짜로 이직을 준비해야할 때라고 생각했었다.

 

그리고 실력좋고 든든했던 동료들이 떠난 뒤에도 남은 인원들끼리 어떻게든 서비스는 유지보수하고 또 새로운 서비스를 만들어나갔다. 
내가 맡은 교육 서비스도 작년에 비해서 더 많은 수강생을 받을 수 있는 LMS로 전환에 성공하고 한 번에 30명 정도밖에 받지 못하던 교육생을 500명 가까이 받을 수 있는 서비스로 탈바꿈했고, 운영팀에서 전담하던 일들을 자동화하여 운영팀의 리소스를 아끼는 일에 집중하였다.

 

사실 초기에는 이런저런 도전과제를 해결하는 것에 집중하다보니 가끔은 머리 아프고 또 가끔은 재밌기도 한 순간들이 많았는데 언젠가부터는 매번 하던 일을 반복하는 상황이 몇달간 지속되었다. 또한 앞으로 해야할 일 조차도 다 비슷한 작업들이었다. 

 

점점 업무에서 성취감을 느끼지 못하는 것은 물론이고 물경력에 대한 불안감이 점점 커지고있던 찰나 파트장님의 퇴사소식을 듣게되었다.
팀장님 없는 우리 파트... 당연히 걱정이 많이 되지만 팀장님도 이런저런 스트레스를 많이 받고있는 것을 직접보고 겪다보니 떠나는 순간만큼이라도 조금이라도 안심시켜드리고 싶은 마음에 덤덤하게 같이 커피마시고 또 점심도 먹으면서 퇴사일까지 아무렇지 않게 지내다가 손흔들며 보내드렸다.

 

파트장님의 빈자리는 팀장님이 떠난 후에야 비로써 느낄 수 있었다.
스프린트가 끝나는 날 사업팀과 같이 회의에 참석해서 다음 스프린트에 할 일을 정했는데, 이제 모든 의사소통을 우리가 직접해야했었다.
작업 가능 여부, 작업 가능 일정 산출 등은 약과였고 불가능한 요구사항에 대해서 설득을 하는 과정이 가장 어렵게 느껴졌던 것 같다. 
가장 기억에 남는 예는 '엑셀 기능을 만들어달라'는 요구사항을 받은 적이 있는데, 이 부분을 설득하는데 나의 설득 스킬이 부족해서 애를 먹었었다.

 

그 외에도 개발자로써 성장하는데 도움이 안되는 업무들이 물밀듯이 밀려왔는데 개인의 성장에 도움만 되는 일을 하는 것은 당연히 지양해야하지만 적당히 개개인의 성취감을 얻게하는 것도 중요하다고 생각했기에 아쉬움이 컸었다. 파트장님이 계실 때는 이런 시간을 마련해주기위해서 많이 노력해주셨고 그 결과 우리에게 조금의 정비시간(?)이 주어져서 이 시간을 통해 조금 더 코드를 다듬거나 생산성을 높이기위한 새로운 툴을 만들기위한 시간으로 활용할 수 있었는데, 파트장님이 떠나신 후로 자연스럽게 이 시간이 없어졌다. 업무시간에 일부라도  개발팀의 생산성을 높일 수 있는 일에 기여할 수 있게 해달라는 요청을 했었지만 받아들여지지 않았었다. 그 외에도 이런저런 일들을 통해서 구성원의 성장에는 신경쓴다고했지만 말로만 그렇게 말한다는 확신이 들었었다.

 

반복되는 업무 속에서라도 뭔가 새로운 성장 포인트를 찾아내려고 노력하던 찰나 가장 큰 든든 요소였던 씨툐님의 퇴사 소식을 듣게되었다.
소식을 처음 접했을 때, 억장이 무너졌다. 아직 누군가를 보내는게 익숙하지 않아서 퇴사자분들한테 찾아가서 인사 잘 못드리는 편이었는데 지금 아니면 진짜 말도 못하고 떠나겠구나 싶은 마음에 아침에 불쑥 찾아가서 커피한잔 같이하자고 말씀드렸고 덕분에 그동안 못했던 이야기들을 니눴었다. 
"어떻게 이렇게 떠날 수가 있어요? 🥹", "가지마요... 😭" 같은 말들을 하고싶었지만 씨툐님이 맘 고생이 더 많으셨을거란 생각이 들어서 차마 그런말은 할 수가 없었다. 이런 슬픔을 전달하는 것 보다 지금까지 감사했던 마음들을 꼭 전달드리면서 마지막 커피챗을 마치고 씨툐님을 떠나보냈다. 

'있다 없으니까'를 절실하게 느낀 순간들이었다.

2) 이직

나의 든든 포인트가 모두 없어지면서 나는 이직을 결심하게 되었다.
하지만 이직 준비를 하면서 처음 알게된 것이 있는데, 취준생 때는 취업 준비하는데만 전념하면 됐지만 이직은 낮에는 일을 해야하니 밤에만 이직준비를 할 수 있었고 시간만 부족한게 아니라 체력도 부족하기 때문에 생각한 것보다 더 쉽지 않다는 것이었다.
나의 나약한 의지로는 이력서 수정을 시작하는 것 조차 쉽지가 않았고 늘 해왔던 것처럼 나를 궁지(?)에 몰아넣어서 억지로 하게만드는 방법을 찾게되었다. 그리고 때마침 평소 애청하던 개발바닥에서 이력서 경진대회를 연다는 소문을 접하고 무슨 자신감에서인지 냅따 지원해버렸다. 발등이 불이 붙으니 부족한 시간과 체력도 이겨내고 조금씩 이직 준비를 하나씩 해나가게됐다.

 

이력서 경진대회를 신청했지만 아쉽게도(?) 입사이후 이력서를 한 번도 갱신하지 않았고 또 경력직의 이력서 쓰는 법을 잘 몰라서 한참 고민하고 여러 레퍼런스를 찾는데 시간을 많이 썼었다. 어느정도 이력서 포맷을 정한 뒤로는 지라를 띄워놓고 지금까지 내가 했던 일들을 모두 리스팅하는 것부터 시작했다. 취준 때 쓰던 이력서 포맷에 리스트 업을 한 뒤, 상대적으로 중요한 것들만 추려내고 불필요한 것들은 삭제했다. 
그리고 중요하다고 생각했던 것들에 대한 구체적인 수치 데이터들을 정리하는데 시간을 쓰다보니 막상 이력서는 별로 다듬지 못했었고, 그 상태로 이력서 경진대회가 시작되었다. 

 

이렇게 이력서 경진대회 전날에 메일이 날아왔고.... 
대망의 이력서 경진 대회의 날이 왔다. 

https://www.youtube.com/watch?v=FOzAGjqiTc0

아쉽게도 준비가 덜 된 이력서다보니 부족한 요소가 확실히 많았기에 결과적으로 공개처형을 당하게 되었다. 😇
마음이 쓰렸지만 존경하는 두 분의 피드백 덕분에 이력서의 어떤 부분을 개선하면 좋을지 또 추후 내가 어떤 것들을 조금 더 보강해야 이력서가 더 다채로워질 수 있을지 알게되었다.
(이 때, 아싸임에도 불구하고 주변에서 연락 엄청 받았다. 😅  개발씬에서의 개발바닥의 위력을 확실하게 체감할 수 있었다)

 

이력서가 어느정도 준비되었다고 생각될 때부터 조금씩 이력서를 넣기 시작했다.
총 20곳에 넣었었고 그중 6곳에 서류 합격을 했고 5곳에 면접을 봤는데, 하나 같이 면접 경험이 좋았다.
그렇다면 1곳은 안좋았냐?라고 한다면 내가 부족해서 안좋았던 것 같다.
평소 '저기서 한번쯤은 일해보고싶다'라는 생각을 했던 회사인데, 아직은 나의 실력과의 Gap 차이가 많이 난다는 것을 느끼게됐다. 
이 면접에서 받았던 대다수의 질문이 내가 작업했던 기능이 대규모 트래픽을 처리해야하는 상황이라면 어떻게 대처해야할까? 와 같은 질문이었는데, 평소에 그런 고민을 크게해보지 않은터라 거의 모든 질문에 답변하지 못했었다. 
이 회사가 원하는 주니어의 기준에는 내 실력이 못미치는구나하고 느꼈고 또 많은 트래픽을 감당하는 방법에 관심을 가지게된 계기가 되었다. 

 

처음 면접봤던 회사에 비해서 뒤에 면접봤던 회사의 면접 난이도가 체감상 낮다고 느껴졌는데, 아마도 면접을 볼 때마다 꾸준히 복기한 것이 큰 도움이됐던 것 같다. 복기를 하면서 내가 답변하지 못한 질문들에 대해 복기하고 어떻게 답변할 것인지 혹은 모르는 개념이였다면 그 내용을 추가적으로 공부해서 다음에는 답변할 수 있는 상태로 만드는 것은 복기를 하는 당연한 이유지만 개인적인 팁으로는 여러 면접을 통해서 나의 이력서를 보고 면접관들이 공통적으로 하는 질문들을 뽑아보았는데 이 부분을 조금 더 힘줘서 확실하게 준비하고 면접장에 들어가니 면접의 초반 분위기를 잘 잡을 수 있었다.

 

결과적으로 거의 막바지에 본 물류 회사 한 곳에 최종합격하게 되었고, 물류 개발자로 커리어를 이어나가게 되었다. 

 

이직을 확정짓고 퇴사 절차를 밟아 퇴사를 하고나니 입사전까지 한달 정도의 여유시간이 남았다.
이 때, 원래라면 한달 푹 쉴 예정이었으나 어머니가 큰 수술을 치르게 되셔서 병간호를 하는데 2주 정도를 보냈다.
다행히 어머니는 수술을 잘 치르셨고, 앞으로 꾸준히 관리가 필요하여 조금 더 식단에 신경쓰고 주기적으로 검진만 잘 받으면 큰 문제가 없다고 이야기를 들었다.

 

어머니의 병간호를 끝마치고 서울에 다시 올라와서는 여자친구와 같이 속초로 (회사 일은 없지만... 리뷰어를 업무를 간간히하기 위해) 워케이션을 갔다. 속초를 이전에 당일치기로 몇시간(?) 있다 가본적은 있지만 이렇게 각잡고 4박 5일을 보내본건 처음이었는데 바다를보며 코드를 짠다는게 생각한 것보다 더 즐거운 일이란 걸 느끼게됐다. (노후에는 바다 근처에서 코딩하며 보내고 싶다는 생각이 들정도로...?)

 

3) 새로운 환경 

놀다보니 남은 2주가 순식간에 지나갔다.
맨날 쉬면 쉬는 것도 질린다고하시는 분들이 있던데 나는 적성에 너무 잘 맞는건지 막상 입사일을 하루 앞둔 시점에도 더 놀고싶다는 생각만 잔뜩 들었다. 🥲

 

입사 일이 OT를 하는 날이라고했다. 
전 회사의 위치도 역삼과 강남 사이의 위치였는데, 이직한 회사도 역삼에 위치해서 출근 루틴이 이전과 다르지 않았다.
떨리는 마음으로 회사 라운지에 올라가서 OT를 받고 배정된 팀의 팀원분들 그리고 그룹원분들과 인사를 나눴다. 

 

확실히 이전의 50명 남짓 있었던 스타트업에서 규모가 큰 기업으로 이직하니 사람이 너무 많아서 정신도 없고 또 직원끼리도 서로 모르는 경우가 허다하다하니 이런 분위기가 많이 낯설었다. 하지만 라운지가 이쁘고 또 사내 카페(특징: 안쌈)가 있다는 점은 마음에 들었다. 

 

어쨋거나 나는 딜리버리프로덕트개발2팀으로 합류하게되었다.
평소 배송 도메인을 맡아서 개발해보고싶었는데, 이렇게 배송 시스템을 담당하는 팀에 와서  많이 기뻣고 팀은 배송/관제와 권역/간선 이렇게 두가지 시스템을 담당하고있는데, 때마침 나는 원하던 배송/관제쪽을 담당하게되었다. (이와는 별개로 권역도 많이 어려워보이고 고민할 포인트가 많은 시스템이라 시간 여유가 될 때마다 코드 살펴보고하려고했는데, 아직도 그럴 여유까지는 없는 것 같다 😂)

 

내가 합류한 시점은 한참 그룹차원에서 매우 바쁜 시점이었다.
기존에는 타회사의 SaaS로 사용했던 서비스를 우리 서비스로 내재화하는 작업이 한참 진행중이었고, 곧 그랜드 런칭을 앞둔 시점이었다.
입사한지 얼마되지않아 도메인 파악하며 작은 티켓들 하나씩 맡아서하다보니 금방 런칭 시작날짜가 왔다. 아무래도 꽤 큰 시스템이다보니 한번에 다 바꾸기보다는 시스템에 조금씩 사용 영역을 확대하는 방식 (= 램프업)으로 점진적으로 시스템을 갈아끼웠다. 

 

이미 동작하고있는 큰 시스템을 교체하는 작업이라니... 
램프업 기간동안 진짜 크고 작은 이슈들이 엄청 터질거같아서 걱정이 많았는데 실상은 그리 크리티컬하지 않은 버그건들만 몇건 제보되는게 전부였다. 팀원들의 피, 땀, 노력이 견고한 시스템을 만들어냈구나라는 생각을 했었고 이런 팀에 합류하게된게 괜히 더 뿌듯해졌었다.
덕분에 개발일하면서 야간 근무해보는 경험은 원없이 해봤다. (물론 앞으로의 개발 생활이 많이 남았으니 앞으로도 할 일이 생기겠지..?)

 

별 탈없이 신규 시스템으로 점차 운영을 확대해나갔고 100%를 찍는 날에는 간단하게 쫑파티를 했다. 
또 신규 시스템으로 완전히 탈바꿈 한 뒤에는 기존의 시스템들의 흔적(?)을 덜어내는 작업들을 하면서 새로운 우리의 시스템으로 교체하는  내재화 작업은 끝이났다.

 

내재화 작업이라는 큰 건이 끝난 뒤부터는 운영 이슈들을 잘 대응하기 위해서 온콜담당(새벽 배송 시스템이다보니 우리 시스템은 새벽시간이 가장 활발하게 사용된다 🥲)을 돌아가며하고있고, 에러로그가 찍히거나 버그 제보가 들어올 때마다 최대한 적극적으로 확인해보면서 조금씩 더 도메인과 친숙해지고있다. 아직도 모르는 영역이 있어서 내년에는 이 부분을 좀 자세히 살펴봐야겠다 생각하고있다. 

 

이제 곧 입사 7개월차가 되가고있는데 지금까지 맡은 업무들도 내 성장에 도움이 많이됐던 작업들이 꽤 많았기에 내년에는 또 어떤일을 맡아서 무럭무럭 성장하게 될지기대된다. 🤩

 

4) 건강 이슈

입사한지 얼마 안되고 발이 너무 아파서 주말에 응급실에 가게됐고 그대로 입원하게됐다. 
부끄럽게도 귀족병이라는 통풍이라고하더라 🥲
또 입원하면서 알게된게 간 수치가 꽤 높다는 사실을 알게됐다. 

 

통풍도 그렇고 간수치도 그렇고 결국 다 살쪄서 그런거라서  내년에는 진짜 살 빼야지.... 😂
✨✨링피트 개시 D-1 ✨✨(작성일 12/31 기준)

 

이 외에도 어머니도 큰 수술을 치르셨고, 아버지도 부정맥으로 응급실행을 다녀오는 등...  올해 우리 가족에게 건강 이슈가 너무 많다. 
액땜이 했다고 생각하고 앞으로 다들 건강관리 잘해서 건강하게 오래오래 볼 수있었으면 좋겠다. 

 

5) X (구: 트위터 🕊️)

요즘 트위터를 애용하고있는데, 처음에는 허공속의 외침 같은 느낌으로 가끔 글을 올렸는데 점점 트친들이 많아지고 
또 간간히 대화주고 받다보니 트위터를 통해서 내적 친밀감을 형성해가는 분들이 점점 늘고있다. (나만 그럴지도...? 🥹)

 

나도 생각보다 낯가림이 좀 있는 상황이다보니 커피챗을 하고싶은 마음은 늘 있었지만 막상 행동으로 옮기지는 못했는데, 4분기에 2분의 트친분들과 커피챗을 하게되었고 그 시간이 재밌고 또 좋은 기억으로 남았던터라 내년에는 조금 더 많은 트친분들과 커피챗을 해보고 싶다. (혹시 이글 보는 트친분들 커피챗☕️ 편하게 요청주십셔...!)

 

트위터에서 요조(트친)님과 대화를주고받은 것이 발단이되어 물리치료 스터디를 만들게되었는데, 한참 열심히 관심을 가지고 이끌어야하는 시기에 하필 건강이슈가 발생해서 생각한 것처럼 잘 운영하지는 못한 것 같다. 😂 그래도 내가 신경쓰지 못하는 동안에 요조님과 노드대장님의 노력으로 어느 정도 스터디가 유지될 수 있었다. 참으로 고마운 분들이다. 🙏

 

내년에는 조금 더 이 스터디를 활성화시키는 것이 목표이고 이런 저런 컨텐츠들을 많이 생각해서 진행해봐야겠다고 생각하고있다. 
또 물리치료 스터디를 조금 더 잘 운영할 수있도록 도와줄 물리치료 프로젝트를 진행하고있는데, 내년에는 이런 시스템적인 장치를 만들어서 나를 포함한 구성원분들이 모두 (묵힌 공부들을) 물리치료하는데 도움이 됐으면 좋겠다. 
막연하게는 (취준 포함) 주니어들이 서로 도움을 주고 받을 수 있는 그런 커뮤니티(?)로 키워나가보고 싶다.

 

마무리 

올 한해는 정말 다이나믹했던 것 같다. 
내년에는 공부도 건강도... 또 네트워킹도 다 챙기는 건강과 함께할 수 있었으면 좋겠다.
또 내년에는 취준 컴퍼니 코치로써 활동하게 됐는데, 나의 미약한 경험들이 취준생분들께 조금이라도 도움이 될 수 있기를 바래보며
(하다못해 커피셔틀이라도... 🙄) 열심히 활동해보려고한다. 
올 한해도 너무 감사한 분들이많은데 그저... 늘 감사합니다. 🙇‍♂️
앞으로도 잘 부탁드리고 내년도 치열하게 살아보시죠~! 화이팅입니다. 💪💪

 

 

728x90
각종 오류 및 해결 방법

Springboot 3 환경에서 Feign Client 에러

728x90

물리치료 스터디에 도움이될만한 사이드 프로젝트를 진행하고있다. 
프로젝트는 SpringBoot 3.x + Kotlin 1.8.22 버전을 사용하고있다. 

해당 프로젝트의 MVP는 프론트 혹은 앱과 같은 클라이언트의 리소스를 들이지 않고, 오로지 Slack API와 통신하며 쓸 수 있는 애플리케이션을 만드는 것이다. 

슬랙 API(= 외부 API)를 사용해야했는데, 이런 Http 클라이언트를 무엇을 쓸지 고민해보다가, Netflix의 Feign 클라이언트가 사용성이 좋았던 기억이 있어서, Feign 클라이언트로 셋티을 했다.

그런데 셋팅을 분명 잘한 것 같은데, 아래와 같은 에러가 계속해서 떳다. 

Consider defining a bean of type 'org.springframework.cloud.openfeign.FeignContext' in your configuration

스택오버플로우를 찾아보니 해당 이슈는 버그로 보이고, 수정되었다고 하는데, 나는 계속 동일한 이슈가 있어서, 아래에 동일한 이슈가 있는데 해결했다는 다른 사람들의 해결책을 따라해보니 해결됐다!

해결 방법

Feign 클라이언트 Configuration 설정 클래스에 FeignAutoConfiguration, HttpClientConfigurationimport 해주면 된다!

@ImportAutoConfiguration({FeignAutoConfiguration.class, HttpClientConfiguration.class})

 

참고자료

https://stackoverflow.com/questions/74593433/consider-defining-a-bean-of-type-org-springframework-cloud-openfeign-feignconte

728x90