블로그

bukak2019

모각코 좋아하는 사람 절대 들어오지마세요 (Feat. 잔디심고갈래?)

🌱 잔디 심고 갈래? 2026년 개발자 플러팅혼자 코딩하지만 함께 몰입하고 있다는 감각을 전달하는 GitHub 기반 온라인 모각코 서비스서비스로 이동!💡 이런 서비스입니다늦은 밤, 혼자 코딩할 때 슬랙에서 동료의 파란 불을 보며 힘을 얻은 적 있으신가요?잔디 심고 갈래? 는 바로 그 느낌을 온라인 공간으로 옮겨왔습니다. 캠·마이크 없이 같은 공간에서 함께 코딩하는 경험을 제공합니다.핵심 가치🌍공동의 성장: 개발자들의 GitHub 활동이 모여 맵을 변화(예: 황무지를 숲으로 변화)시킵니다⏱집중의 시각화: 커밋, PR, 집중 시간이 실시간으로 반영됩니다🐾펫과 함께: 활동으로 얻은 포인트로 펫을 뽑고 성장시킵니다🤝느슨한 연대: 캠/마이크 없이, 같은 공간에서 함께 코딩하는 경험을 제공합니다✨ 주요 기능1⃣ 함께 성장하는 가상 세계모두의 활동이 모여 맵이 변화. 7일 시즌제로 주기적 리셋이 이루어지고, 테마가 변경됨.테마2⃣ 실시간 캐릭터 시스템GitHub 프로필·마스코트 캐릭터로 표현. 닉네임·집중시간·태스크 표시, 실시간 상호작용.3⃣ GitHub 활동 자동 연동 및 리더보드OAuth 간편 로그인, 30초 간격 자동 감지. 활동 시 포인트가 쌓이고, 포인트에 따라 맵 변화 게이지 상승.리더보드4⃣ 개인 성취 시각화잔디·캘린더를 통한 날짜별 집중시간·태스크·활동 내역 확인히팅맵 및 개인잔디포인트 획득에 따라 달라지는 개인 잔디에서 수집한 펫과 활동들을 확인 가능세부 활동해당 날짜에 진행한 깃허브 활동 상세 내역이나 자신의 Task(To-do)를 확인 가능5⃣ 펫 시스템 🐾포인트로 가챠·육성·진화(3단계). 대표 펫이 캐릭터 동행.펫과 캐릭터 이동펫 뽑기펫을 뽑아 새로운 펫들을 수집 가능펫 도감펫 도감을 통해 수집한 펫들을 관리하고, 대표 펫으로 설정하여 함께 이동 가능펫 밥주기펫에게 밥을 주어 다음 단계로 성장시켜, 도감 수집 가능

네이버부스트캠프잔심갈잔디심고갈래?모각코Github개발자플러팅

jhworld

2월 서바이벌 챌린지와의 첫 만남썰 푼다

1월에 단기심화과정이 끝나고 2월부터 야생에 던져진.. ㄴr 란 girl...강제성이 있는 100% 스터디나 커뮤니티에 가입되어야만 엉덩이가 무거워지는 P girl이었다.프로젝트에 집중한 나머지 알고리즘에 너무 손을 떼버려“인강으로 빠르게 복습해야지~” 하고 인프런에 들어가자마자 반겨주는인프런 2월 챌린지 배너심상치 않았다.그런데 참가비가 있네?나 가난한 1인가구 가장인데..어? 열심히만 하면 이 돈 다 내 것이 될 수 있다고..?(3백이었나 4백이었나 아무튼 x백 어쩌고)바로 신청 갈겼다.이것이 바로 인프런 챌린지와의 첫 만남이었다.해당 챌린지에 대해 설명을 듣기 위해 라이브에 참석했다.그리고 심상치 않은 어떤 그녀를 발견했다.그녀의 이름은 바로, 도라 (님)알 수 없는 끌림에 (솔직히 재밌어서) 모든 라이브를 본방사수했다 ㅋ얼떨결에 라이브까지 열심히 참여한 사람이 되어버렸다.아, 이것 또한 성취감인가.3주 동안 학습하면서 30분을 채워야 했기에1분이 모자르면 “하나 더! 하나 더!! 그래야만 살아남을 수 있어!”하면서 학습을 이어갔다.말 그대로 챌린지이기에 평일에만 하는 것이 아니라주말에도 “조금만 더! 조금만 더!” 이러고 있었다.덕분에 학습 분위기가 형성되었고 습관이 생겼다.또한 학습 시간도 점점 늘어나니챌린지에 감동받은 나를 발견할 수 있었다.그래서 열심히 주변 사람들에게 소개를 했고4월 챌린지도 아는 분과 같이 진행한다 ㅎ챌린지를 진행하면서 공부도 하는데 돈도 줘?아 물론 참가비도 내지만, 참가비보다 더 받을 수 있다.(생존만 한다면)아쉬운 점?언제 한 번 도라님이 아프셔서 걱정되었는데 그게 좀 슬펐달까?그리고 라이브 랜덤 포인트 뽑기에서나만 1000 이상 받은 적 없다는 것도 아쉽다랄까?아쉬운 점을 더 찾는다면 다음 3월 챌린지 후기에서 써보도록 하겠다. (참고로 상금 받았다 헤)인프런 챌린지 추천합니다!

커리어 · 자기계발 기타인프런챌린지인프런2월챌린지도라시몬

minsu

2월 서바이벌 챌린지 후기(사실 1월 챌린지도 포함)

취업을 준비중인데, 작년까지는 학원에 다니며 강제성이 있는 공부를 했다. 하지만 당장 올해 1월 1일부터는 혼자서 공부를 해야 했다. 마침 인프런에서 말 달리자 챌린지가 열려서 바로 참가했고 환급은 물론 기프티콘까지 받았다. 1월 챌린지 기간 동안 이틀 빼고 매일 공부하며 조금씩 기록한 것을 좋게 봐주신 것 같다. 자연스럽게 2월 챌린지도 관심을 가지게 되었고 서바이벌 형식의 챌린지인데, 생존자 수에 따른 상금을 준다고 해서 재미있을 것 같아 바로 신청했다. 이번에는 완강 여부와 상관없이 거의 매일 공부하는 것이었는데 사실 1월 챌린지로 이미 거의 매일 공부하는 습관이 잡혀서 미션이 어렵게 느껴지지 않았다. 또한 챌린지를 통해 단순히 매일 공부하는 습관만 잡은게 아니고 하루의 공부 루틴을 세울 수 있었다. 아침에는 꼭 필요하지만 잘 손이 가지 않는 CS 지식이나 프로그래밍 언어에 대해 공부하며 챌린지 미션을 제출하고, 그 후에는 포트폴리오를 제작하거나 제작하기 위한 지식에 대해 학습하는 것이다. 오늘은 3월 17일인데 3월 챌린지도 참여하며 똑같은 루틴을 지키는 중이다. 사실 온라인 교육 플랫폼에서 가장 중요한 것은 강의를 많이 팔아서 수익을 올리는 것일 것이다. 그런데 인프런은 이런 챌린지를 통해 사용자들이 정말로 학습하도록 도와준다는 점에서 진정한 교육 플랫폼이라고 느꼈다. 다음 달에도 인프런과 함께 달려보려고 한다.

챌린지

양성빈(Robert)

K2 컴파일러가 바꿔놓은 Java SAM 변환의 모든 것

들어가며최근 Kotlin 버전을 1.8에서 2.3으로 올리고 코드를 작성하던 중, 흥미로운 현상을 발견했다.package me.sungbin.function fun main() { val filter: StringFilter = { s -> s.startsWith("A") } }StringFilter는 Java에서 정의한 함수형 인터페이스다. 그런데 이 코드가 Kotlin 1.8에서는 컴파일 에러가 나고, Kotlin 2.3에서는 정상 동작한다. 분명 Java SAM 인터페이스에 람다를 직접 대입하는 건 안 되는 걸로 알고 있었는데, 왜 지금은 되는 걸까? 이 글에서는 이 동작 변화의 원인을 파헤쳐 보고, Kotlin의 SAM 변환이 어떻게 발전해 왔는지를 정리해 본다.SAM 변환이란?SAM은 Single Abstract Method의 약자로, 추상 메서드가 하나만 있는 인터페이스를 말한다. Java 8에서는 이런 인터페이스를 함수형 인터페이스(Functional Interface) 라고 부르며, @FunctionalInterface 어노테이션을 붙여 명시한다.@FunctionalInterface public interface StringFilter { boolean filter(String s); }Java에서는 이런 함수형 인터페이스 타입이 기대되는 곳에 람다를 직접 전달할 수 있다. 이걸 SAM 변환(SAM Conversion) 이라고 한다.// Java — 람다를 함수형 인터페이스 타입 변수에 직접 대입 StringFilter filter = s -> s.startsWith("A");Kotlin도 Java와의 상호운용성을 위해 SAM 변환을 지원한다. 하지만 그 지원 범위는 Kotlin 버전에 따라 달랐다.Kotlin에서의 SAM 변환 역사Kotlin 1.0 - Java SAM에 대한 기본 지원Kotlin은 1.0부터 Java의 함수형 인터페이스에 대한 SAM 변환을 지원했다. 다만 지원되는 위치가 제한적이었다. 함수 인자로 전달할 때는 SAM 변환이 적용됐다.// Java 메서드: void applyFilter(StringFilter filter) { ... } // 함수 파라미터로 전달 — SAM 변환 적용 applyFilter { s -> s.startsWith("A") }SAM 생성자(SAM Constructor) 를 명시적으로 사용하는 것도 가능했다.// SAM 생성자 — 항상 가능 val filter = StringFilter { s -> s.startsWith("A") }하지만 변수 타입을 명시하고 람다를 직접 대입하는 것은 불가능했다.// Kotlin 1.x에서 컴파일 에러! val filter: StringFilter = { s -> s.startsWith("A") } // Type mismatch: inferred type is (String) -> Boolean but StringFilter was expectedKotlin 1.4 - Kotlin 인터페이스에 대한 SAM 변환 (fun interface)Kotlin 1.4에서는 fun interface 키워드가 도입되면서, Kotlin에서 정의한 인터페이스에도 SAM 변환을 사용할 수 있게 됐다.// Kotlin 1.4 이전에는 이게 안 됐음 interface MyFilter { fun filter(s: String): Boolean } val f = MyFilter { s -> s.startsWith("A") } // 컴파일 에러 // Kotlin 1.4부터 fun interface로 선언하면 SAM 변환 가능 fun interface MyFilter { fun filter(s: String): Boolean } val f = MyFilter { s -> s.startsWith("A") } // OK이 시점에서 많은 개발자들이 "Kotlin에서 SAM 변환이 안 된다"고 알고 있던 것은, 바로 이 Kotlin 인터페이스에 대한 SAM 변환이 안 됐던 것을 기억하는 경우가 많다. 하지만 fun interface 도입 이후에도, 타입을 명시한 변수에 람다를 직접 대입하는 것(val f: Type = { ... })은 Java SAM, Kotlin fun interface 모두에서 불가능했다. 1.4에서 가능해진 것은 SAM 생성자(val f = MyFilter { ... })와 함수 인자 전달 위치에서의 SAM 변환이었다.Kotlin 2.0 - K2 컴파일러와 SAM 변환의 확장그리고 마침내 Kotlin 2.0에서 K2 컴파일러가 정식 도입되면서, 이 제한이 해제됐다.// Kotlin 2.0+ (K2 컴파일러)에서는 이것도 된다! val filter: StringFilter = { s -> s.startsWith("A") }구 컴파일러는 왜 이걸 허용하지 않았을까?이유를 이해하려면, 구 컴파일러의 타입 추론 방식을 알아야 한다.구 컴파일러의 타입 추론 흐름구 컴파일러가 val filter: StringFilter = { s -> s.startsWith("A") }를 만났을 때, 내부적으로 다음과 같은 순서로 처리했다.우변의 람다 타입 결정: { s -> s.startsWith("A") }는 (String) -> Boolean 함수 타입으로 추론된다.좌변의 기대 타입 확인: StringFilter는 Java 인터페이스 타입이다.타입 호환성 검사: (String) -> Boolean != StringFilter -> Type mismatch!구 컴파일러는 SAM 변환을 특정 위치에서만 적용했다.함수 호출 시 인자로 전달하는 위치SAM 생성자를 명시적으로 사용하는 경우이는 구 컴파일러의 프론트엔드 아키텍처가 BindingContext라는 거대한 해시 테이블 기반 구조에 의존했기 때문이다. 타입 정보를 단계별로 수집하고 저장하는 이 방식에서는, 대입문의 기대 타입 정보를 람다의 타입 추론 단계에 자연스럽게 전달하기가 구조적으로 어려웠다.SAM 생성자로 우회하는 방법그래서 구 컴파일러에서는 SAM 생성자를 사용해 명시적으로 변환을 지시해야 했다.// SAM 생성자 — 컴파일러에게 "이 람다를 StringFilter로 변환해라"고 명시적으로 알려줌 val filter = StringFilter { s -> s.startsWith("A") }SAM 생성자는 컴파일러가 자동으로 생성하는 팩토리 함수처럼 동작하며, 람다를 해당 인터페이스의 구현체로 명시적으로 감싸준다.K2 컴파일러는 무엇이 다른가?완전히 새로 작성된 프론트엔드K2 컴파일러는 Kotlin 컴파일러의 프론트엔드(의미 분석, 호출 해석, 타입 추론 담당) 를 완전히 새로 작성한 것이다. 공식 문서에서는 이를 다음과 같이 설명한다.With the arrival of the K2 compiler, the Kotlin frontend has been completely rewritten and features a new, more efficient architecture. The fundamental change the new compiler brings is the use of one unified data structure that contains more semantic information.- K2 compiler migration guide구 컴파일러 vs K2 컴파일러의 내부 구조 차이구 컴파일러는 PSI(Program Structure Interface) 와 BindingContext에 의존했다.PSI는 소스 파일의 모든 정보를 담고 있어 크고 복잡하다.BindingContext는 바인딩 정보를 거대한 해시 맵 구조로 관리했다.변수 참조 하나를 조회하는 데도 여러 번의 맵 조회가 필요했다.K2 컴파일러는 FIR(Frontend Intermediate Representation) 이라는 새로운 트리 기반 데이터 구조를 사용한다.FIR은 PSI보다 간결하면서도 더 많은 의미 정보를 포함한다.트리 노드에서 직접 값을 접근하므로 해시 맵 조회가 필요 없다.타입 추론 시 기대 타입 정보가 자연스럽게 하위 노드로 전파된다.K2에서의 타입 추론 흐름K2 컴파일러가 동일한 코드를 처리할 때는 이렇게 동작한다.좌변의 기대 타입 확인: StringFilter가 기대된다.기대 타입 정보를 우변으로 전파: 람다에게 "네가 StringFilter가 되어야 한다"는 정보를 전달한다.SAM 변환 가능 여부 확인: StringFilter는 Java 함수형 인터페이스이고, 람다의 시그니처가 filter(String): Boolean과 일치한다.암시적 SAM 변환 적용: 람다를 StringFilter 구현체로 자동 변환한다.핵심 차이는 K2 컴파일러가 기대 타입(Expected Type) 정보를 적극적으로 활용한다는 것이다. 구 컴파일러에서는 대입문의 기대 타입 정보가 SAM 변환 판단에 반영되지 않았지만, K2에서는 기대 타입이 SAM 인터페이스인 모든 위치에서 암시적 SAM 변환이 가능해졌다.실제 코드로 보는 동작 차이다양한 케이스에서 Kotlin 1.x와 2.0+의 동작 차이를 정리해 보자.Case 1: 변수 대입// Java @FunctionalInterface public interface StringFilter { boolean filter(String s); }// Kotlin val filter: StringFilter = { s -> s.startsWith("A") }Kotlin 1.x: Type mismatchKotlin 2.0+: 정상 컴파일Case 2: SAM 생성자 (명시적)val filter = StringFilter { s -> s.startsWith("A") }Kotlin 1.x: 정상 컴파일Kotlin 2.0+: 정상 컴파일Case 3: 함수 인자로 전달fun applyFilter(filter: StringFilter) { /* ... */ } applyFilter { s -> s.startsWith("A") }Kotlin 1.x: 정상 컴파일Kotlin 2.0+: 정상 컴파일Case 4: 함수 반환 타입으로 사용fun createFilter(): StringFilter { return { s -> s.startsWith("A") } }Kotlin 1.x: Type mismatchKotlin 2.0+: 정상 컴파일Case 4에서 볼 수 있듯이, K2 컴파일러는 변수 대입뿐만 아니라 기대 타입이 명확한 모든 위치에서 SAM 변환을 적용한다.흥미로운 점은, Java 함수형 인터페이스뿐만 아니라 Kotlin fun interface에서도 변수 대입(val f: Type = { ... }) 방식은 1.x에서 동작하지 않았다는 것이다. 1.4에서 fun interface가 도입됐을 때 지원된 것은 SAM 생성자(val f = MyFilter { ... })와 함수 인자 전달이었고, 타입을 명시한 변수에 람다를 직접 대입하는 방식은 K2 컴파일러에서야 가능해졌다. 결국 K2의 기대 타입 기반 SAM 변환 확장은 Java SAM과 Kotlin fun interface 모두에 적용된 범용적인 개선인 셈이다.K2 컴파일러가 가져온 그 외의 개선들K2 컴파일러는 SAM 변환 외에도 다양한 타입 추론 개선을 포함하고 있다. 대표적인 것들을 간략히 살펴보자.스마트 캐스트 개선class Cat { fun purr() { println("야옹") } } fun petAnimal(animal: Any) { val isCat = animal is Cat if (isCat) { // Kotlin 2.0+: isCat 변수를 통해 animal이 Cat으로 스마트 캐스트됨 // Kotlin 1.x: 스마트 캐스트 불가 — 변수에 담긴 조건식은 인식하지 못했음 animal.purr() } }논리 OR 연산자와 스마트 캐스트interface Status { fun signal() { println("Signal received") } } interface Postponed : Status interface Declined : Status fun signalCheck(signalStatus: Any) { if (signalStatus is Postponed || signalStatus is Declined) { // Kotlin 2.0+: 공통 상위 타입인 Status로 스마트 캐스트 // Kotlin 1.x: Any로 캐스트되어 signal() 호출 불가 signalStatus.signal() } }인라인 함수 내에서의 스마트 캐스트interface Processor { fun process() { println("Processing...") } } inline fun inlineAction(f: () -> Unit) = f() fun nextProcessor(): Processor? = object : Processor {} fun runProcessor(): Processor? { var processor: Processor? = null inlineAction { if (processor != null) { // Kotlin 2.0+: processor가 non-null로 스마트 캐스트 // Kotlin 1.x: safe call 필요 (processor?.process()) processor.process() } processor = nextProcessor() } return processor }마무리정리하면, Kotlin 1.x에서 Java 함수형 인터페이스 타입 변수에 람다를 직접 대입하지 못했던 것은 구 컴파일러의 타입 추론 한계 때문이었다. Kotlin 2.0에서 도입된 K2 컴파일러는 프론트엔드를 완전히 재작성하면서 기대 타입 기반의 SAM 변환을 더 넓은 범위에서 지원하게 됐고, 그 결과 이전에 불가능했던 코드가 자연스럽게 동작하게 됐다. 이처럼 K2 컴파일러는 단순한 성능 개선뿐만 아니라, 개발자가 "당연히 될 것 같은데 안 됐던" 코드들을 실제로 동작하게 만들어 주는 의미 있는 변화를 가져왔다. Kotlin 2.0 이상을 사용하고 있다면, 한번 실무에 적용해보는 것도 좋은 방법 같다. 혹여나 틀린 지식이 있을 경우 바로 알려주시면 정정하겠습니다. 참고자료- https://kotlinlang.org/docs/k2-compiler-migration-guide.html- https://kotlinlang.org/docs/whatsnew20.html- https://kotlinlang.org/docs/fun-interfaces.html- https://kotlinlang.org/docs/compatibility-guide-20.html

백엔드kotlinjavak2-complier

송하경

2월 서바이벌 챌린지 후기 — 돈을 벌었습니다 (그게 전부가 아니에요)

챌린지에 참여하게 된 계기작년 12월에 첫 이직에 성공하여 AI 엔지니어로 전향한 지 얼마 되지 않아 배워야 할 건 산더미이고, 작업 해야할 양도 많은 상황이였다보니 잘 할 수 있을까? 라는 생각이 들었지만, "이왕 공부할 거, 보상도 받으면 좋지 않나" 라는 가벼운 마음으로 1월부터 인프런 챌린지 이벤트를 꾸준히 참여해 오고 있었다. 처음엔 그냥이었는데, 막상 해보니 생각보다 훨씬 강력한 동기부여가 된 것 같다.2월 서바이벌 챌린지는 2월 2일부터 25일까지, 딱 3주 동안 매일 최소 30분 이상 학습하고 인증하는 방식이었다. 735명이 함께 참가비를 냈고, 끝까지 살아남은 사람들이 총 6,615,000원을 나눠 갖는 구조. "매일 30분 이상" 이라는 조건이 핵심이었다. 부담스럽지 않으면서도, 매일 한다는 게 생각보다 어렵다는 딱 그 지점을 건드린 챌린지라는 게 느껴졌다.3주 동안 학습하면서 느낀 점자정이 기다려지는 신기한 경험챌린지가 진행되는 동안 매일 자정 이후에 "오늘의 생존자" 현황이 업데이트됐다. 처음엔 그냥 지나쳤는데, 어느 순간부터 자정이 되면 자연스럽게 해당 페이지를 찾아보게 됐다. 오늘은 몇 명이 떨어졌지? 이게 묘하게 재밌었다. 남들이 떨어졌다는 게 기쁜 게 아니라(물론 기쁘기도 했음), 어려운 걸 잘 해내고 있다는 증명 같은 느낌이랄까. 매일 밤 작은 성취감을 확인하는 루틴이 생겼다. 라이브 방송이 의외의 활력이 됐다중간중간 라이브 방송도 있었는데, 챌린지에 참여하고 있는 사람들이 모여서 도라님 중심으로 막 수다떠는 시간이 생각보다 좋았다. 도라님의 남다른 텐션에 도라버릴 뻔한 적도 있었지만 집에서 혼자 공부하고는 있지만 혼자가 아닌 것 같다는 느낌을 받아서 위안이 되면서 또 자극도 됐다.30분이 30분이 아니었다솔직히 처음엔 "30분만 하고 인증하면 되겠지" 라고 생각했다. 근데 막상 앉아서 시작하면 30분을 그냥 넘기는 날이 훨씬 많았다. 시작 자체가 제일 어렵다는 걸 다시 한번 느꼈다. 챌린지가 "일단 앉게 만드는" 트리거 역할을 해준 것 같다는 생각이 들었다.챌린지를 통해 생긴 변화회사에서 서비스를 개발하면서 공부가 곧 일이고, 일이 곧 공부인 상황이다. 그러다 보니 "퇴근 후 공부" 라는 게 의무감으로 느껴질 때가 많았다. 그런데 챌린지를 하면서 달라진 게 있다면, 공부를 하루 루틴의 일부로 받아들이게 됐다 인 것 같다. 그래서 "오늘 인증했나?" 라고 스스로 점검하는 습관이 생겼고, 챌린지가 끝나도 그 후유증(?)으로 고통(?)을 겪었지만 3월 챌린지를 시작하니 오히려 마음이 편해지는 기이한 현상을 경험했고 지금도 꾸준히 인프런 챌린지에 참여하고 있다. (4월 챌린지도 이미 신청했다. 이정도면 챌린지 중독인가...)좋았던 점 / 아쉬웠던 점좋았던 점학습 인증 과정이 복잡하지 않아서 부담이 없었다. 진입 장벽이 낮아서 오히려 더 꾸준히 하게 됐다.자정 이후에 발표되는 생존자 현황이 작은 게임 요소처럼 작동해서, 매일 미션을 잊어버리지 않게 해준 것 같다. (절대 탈락하지 않겠어!)혼자 공부하는 외로움을 함께 달려가는 느낌으로 바꿔줬다.챌린지 종료 후 22,500원을 돌려받았다. 액수가 크고 작고의 문제가 아닌, "해냈다" 는 걸 증명받은 느낌이였다.아쉬웠던 점챌린지가 종료된 이후에 갑자기 외부 동기가 사라지는 느낌이 있었다. 살짝 공백이 오는 느낌? 스스로 연결고리를 만들어두지 않으면 흐지부지될 수 있겠다 싶었다. (그래서 4월 챌린지도 신청한 것)마지막 소감솔직히 인프런 챌린지를 시작할 때 "공부 습관을 만들겠다" 라는 거창한 목표는 없었다. 그냥 어차피 해야 할 공부, 다른 사람들과 같이 하면 덜 힘들지 않을까 싶었다. 그런데 1월부터 2월, 3월, 그리고 4월까지 이어지는 걸 보면 이미 답이 나온 것 같다. 챌린지가 나를 바꾼 게 아니라, 챌린지가 내 안에 있던 무언가를 꺼내준 느낌. 지금 공부는 하고 싶은데 혼자는 흐지부지 작심삼일 될 것 같다면, 이런 챌린지를 통해 한 번쯤 함께 달려보는 것도 나쁘지 않다고 생각한다. (나는 다음 달에도 여기 있을 예정)

커리어 · 자기계발 기타인프런서바이벌챌린지학습습관2월챌린지후기

최동준

스팸 메일과 섞인 뉴스레터, 이제 그만 독립시켜 주세요!

안녕하세요. 다들 긱뉴스(GeekNews)나 요즘 IT와 같은 직무 관련, 개발 관련 뉴스레터 몇 개씩은 구독하고 계실 텐데요. 혹시 메일함에 차곡차곡 쌓아두기만 하고 막상 잘 안 읽게 되지 않으신가요?저도 성장을 위해 여러 뉴스레터를 구독했지만, 쇼핑몰 주문 완료 메일이나, 스팸/광고 메일과 뒤섞이다 보니 결국 나중에 한 번에 지워버리는 일이 반복되더라구요! 이런 불편함을 해결하려고 뉴스레터 메일만 따로 분리해 읽기 편하게 만들어주면서 읽을 동기 부여도 주는 리딩 플랫폼, 봄봄을 만들게 되었습니다. 평소 저와 비슷한 아쉬움을 느끼셨던 분들께 도움이 될거 같습니다! 이메일함과 분리된 '투데이' 페이지복잡한 메일함에 들어갈 필요 없이, 내가 구독한 뉴스레터의 새 아티클들만 피드 형태로 모아볼 수 있습니다. 하루에 한 번 '투데이' 탭만 가볍게 훑어봐도 그날의 트렌드를 놓치지 않고 확인할 수 있습니다. 텍스트 본연에 집중할 수 있는 뷰어글을 읽을 때 방해가 되는 시각적 요소들을 최소화해 텍스트 몰입도를 높였습니다. 나중에 실무에 참고하고 싶은 인사이트는 북마크해 둘 수 있고, 하이라이트를 치거나 메모를 남기며 나만의 지식을 차곡차곡 기록할 수 있습니다.습관 형성을 돕는 동기 부여 요소뉴스레터를 꾸준히 읽도록 돕기 위해 함께 읽고 코멘트를 공유하는 '읽기 챌린지'를 운영하고 있습니다. (실제 참여자분들의 만족도가 아주 높으니 꼭 한번 참여해 보시기를 추천해 드립니다.)또한, ‘이달의 독서왕’이라는 랭킹을 통해 다른 유저들과 건강한 자극을 주고받으며 습관 형성을 이어갈 수 있습니다. 연속 읽기 기록(Streak)을 쌓고, 성취도에 따라 귀여운 '봄이' 캐릭터가 조금씩 성장하는 과정을 보는 소소한 재미도 챙겼습니다.웹은 물론 안드로이드, iOS 앱으로도 이용하실 수 있습니다. 모바일에서 이용하실 경우 아티클 도착 알림을 통해 더욱 잊지 않고 보실 수 있게 도와드리고 있습니다!평소에 유익한 뉴스레터들을 더 잘 챙겨 보고 싶으셨거나, 인박스 관리가 번거로우셨던 분들이라면 가볍게 한번 써보시면 좋을 것 같습니다.👉 봄봄 한번 둘러보기👉봄봄 앱스토어 바로가기👉 봄봄 플레이스토어 바로가기사용해 보시고 주시는 다양한 피드백과 의견은 언제나 환영입니다. 긴 글 읽어주셔서 감사합니다!

뉴스레터챌린지봄봄뉴닉데일리바이트뉴스트렌드IT

ds3400

[일본IT취업연계] 풀스택 개발 과정 모집! 정보처리산업기사 취득

[정보처리산업기사] 자바(JAVA) 기반 풀스택 개발자 과정 모집개발자 취업을 준비하는 분들을 위한자격증 + 실무 + 취업 연계형 국비지원 과정 안내드립니다. [과정 개요]과정명 : 정보처리산업기사 취득 자바 기반 풀스택 개발 과정개강일 : 2026년 6월 09일교육기간 : 2026.06.09 ~ 2026.11.20교육시간 : 09:10 ~ 17:50 (주간)교육장소 : 부산진구 중앙대로 668, 에이원프라자 6층 [과정 특징]정보처리산업기사 취득 가능과정평가형 자격 취득 과정학력·전공 무관 지원 가능교육 이수 + 평가를 통한 자격증 취득 2.자바 기반 풀스택 개발 교육JAVA 프로그래밍 기초 및 심화웹 개발 (프론트엔드 + 백엔드)데이터베이스 설계 및 활용실무 프로젝트 및 포트폴리오 제작 3.취업 연계 지원국내 IT기업 취업 지원일본 IT기업 취업 연계 가능협약기업 연계 및 취업 컨설팅 제공 [교육 혜택]국민내일배움카드 적용 과정매월 훈련장려금 지급 (약 20~80만원)취업 지원 및 컨설팅 제공 [지원 대상]IT 개발자 취업을 목표로 하는 분비전공자이지만 개발을 배우고 싶은 분자격증 취득과 실무 역량을 함께 준비하고 싶은 분국내 및 일본 취업을 고려 중인 분 [문의 및 접수]교육기관 : 동성인재개발교육원문의전화 : 051-933-3400홈페이지 : https://www.dshrd.or.kr/course_a.html?eduPart=1해당 과정은 정원 마감 시 조기 종료될 수 있으니관심 있는 분들은 빠르게 문의해보시길 바랍니다

풀스택일본취업일본취업연계일본취업비자일본IT취업IT학원자바학원부산자바학원부산IT학원동성인재개발교육원부산개발자학원

하늘소녀

Do it! HTML + CSS 웹 표준의 정석 - 겨울 방학 맞이 기초 언어 스터디(6)

#지난주에 이어서...CSS로 화면을 설계하고,자바스크립트로 동작을 붙였다면,6주차는 드디어 웹 문서를 ‘직접 조작하는 단계’로 들어섰다. # 자바스크립트와 객체자바스크립트가 왜 객체 기반 언어인지 이해하는 장이다.객체의 개념자바스크립트의 내장 객체브라우저와 관련된 객체* 이제 코드가 단순한 명령어 모음이 아니라속성과 기능을 묶은 구조(객체)로 보이기 시작했다.웹 브라우저 자체도 하나의 거대한 객체 시스템이라는 점이 인상적이었다.# 문서 객체 모델(DOM) 다루기드디어 DOM 등장.문서 객체 모델 이해하기DOM 요소 접근하기내용 수정하기이벤트 처리하기노드 추가·삭제class 속성 추가·제거* 여기서부터 진짜 “웹을 조작한다”는 느낌이 들었다.HTML은 구조,CSS는 디자인,JavaScript는 동작이라고 배웠지만DOM을 배우고 나니이 세 가지가 완전히 연결되는 지점이 보였다.버튼을 클릭하면특정 요소를 찾아내용을 바꾸고클래스를 추가해 스타일을 변경하는 흐름이제야 비로소“프론트엔드의 기본 구조”가 머릿속에서 연결됐다.# 6주차를 마치며 – 완주!처음엔 단순히“HTML부터 다시 정리해보자”는 마음으로 시작한 스터디였는데,6주를 마치고 보니 웹이 어떻게 만들어지고, 어떻게 동작하는지 한 사이클을 다 돌았다는 느낌이다.6주후 내가 얻은 것웹의 구조 이해 (HTML)화면 설계 능력 (CSS)로직과 상호작용 (JavaScript)문서 조작 능력 (DOM)이제는 단순히 코드를 따라 치는 게 아니라“왜 이렇게 동작하는지”를 설명할 수 있는 단계까지 온 것 같다.정말 긴 여정이었지만, 매주 기록을 남기며 공부한 덕분에 흐름이 훨씬 또렷하게 정리됐다.

웹 퍼블리싱HTMLCSSjavascript웹표준스터디DOIT

ICT TMD

[무료교육] AI 스마트관광 비즈니스 & 마케팅 마스터 과정 교육생 모집

🔍이제는 ‘AI를 배우는 것’을 넘어 실제 업무에 적용하는 방법을 익힐 시간!“AI는 써봤는데, 실제 업무에 어떻게 활용해야 할지 모르겠어요.”“콘텐츠 만들기는 하는데, 전략까지 연결하기가 어렵습니다.”“기획부터 마케팅까지 한 번에 배울 수 있는 교육이 필요해요.” 위와 같은 고민을 가진 분들을 위해,이번 교육은기획 → 분석 → 브랜딩 → 콘텐츠 → 마케팅 → 자동화까지전 과정을 실습 중심으로 경험할 수 있도록 구성되었습니다! 📍신청 기간 : 3월 31일(화) ~ 4월 10일(금) 📍교육 일정📅기간: 2026년 4월 21일(화) ~ 2026년 6월 2일(화)⏰시간: 매주 화,목 19시 30분~ 22시 30분💻장소: 온라인(ZOOM 실시간 교육)👥대상: 만 17세 이상 제주 거주자 누구나 신청 가능 📚교육 내용- AI 기반 시장 분석 및 비즈니스 기획 실습- 브랜드 전략 수립 및 콘텐츠 제작 (이미지 · 영상 · 카피라이팅)- 고객 여정 기반 마케팅 전략(IMC) 설계-AI 에이전트 활용 업무 자동화 및 실무 적용 🛠 사용 AI 실습 툴 (무료)ChatGPT, Gemini, Claude, NotebookLM, Vrew, Canva 등※ 별도 유료 결제 없이 실습 가능한 도구 중심으로 진행 🎁 교육생 혜택1. 교육비 전액 무료 100% 국비 지원2. 온라인 복습영상 제공3. 우수 출석율 프로모션 이벤트 (최대 2만원 모바일 상품권 증정)4. 우수 프로젝트 시상 (최대 3만원 모바일 상품권 증정)5. 70% 출석 시 수료증 발급 👉🏻신청 바로가기 : https://forms.gle/UruUKh9yeDE8k5gt7☎ 문의 ☎ 064-904-1443 (제주 ICT이노베이션)

AI 크리에이티브AIgptai에이전트업무자동화마케팅무료교육인공지능디지털마케팅브랜드마케팅마케팅전략

Go Hard

[Python] Matplotlib을 활용한 F1 레이스카 궤적 이탈 시뮬레이션 구현

안녕하세요.오늘은 파이썬의 NumPy와 Matplotlib 애니메이션 기능을 활용해, F1 차량의 코너링 중 접지력 상실(Hydroplaning) 시점을 계산하고 궤적을 시각화하는 시뮬레이션을 구현해 보았습니다.개발을 하다 보면 종종 '모든 물리 변수를 정밀하게 넣어야 완벽한 코드'라는 이론적 강박에 빠지곤 합니다. 하지만 목적이 '데이터 추출'이 아닌 '현상의 시각화'라면, 모델의 복잡도를 낮추는 것이 렌더링 최적화와 직관성 측면에서 현실적인 정답이 될 수 있습니다.아래는 차량의 현재 위치와 속도, 중력가속도를 계산하여 그립(Grip) 상실 여부에 따라 궤적 벡터를 분기하는 핵심 코드 블록입니다.def calculate_state(t, v0, r, drag_coef): dt_step = t if t < config['t_hydroplaning'] else config['t_hydroplaning'] omega = v0 / r # 정상적인 코너링 궤적 계산 initial_theta = np.pi current_theta = initial_theta + omega * dt_step car_x = r * np.cos(current_theta) + r car_y = r * np.sin(current_theta) + r # 수막현상 발생 시점 이후의 관성 이동 벡터 계산 if t >= config['t_hydroplaning']: lost_grip_theta = initial_theta + omega * config['t_hydroplaning'] lost_grip_pos = np.array([r * np.cos(lost_grip_theta) + r, r * np.sin(lost_grip_theta) + r]) lost_grip_v_vec = np.array([-v0 * np.sin(lost_grip_theta), v0 * np.cos(lost_grip_theta)]) slide_time = t - config['t_hydroplaning'] slide_accel = drag_coef * v0 slide_v_mag = max(0, v0 - slide_accel * slide_time) slide_v_avg = (v0 + slide_v_mag) / 2 slide_dist = slide_v_avg * slide_time v0_unit_vec = lost_grip_v_vec / v0 if v0 > 0 else np.array([0, 0]) car_x = lost_grip_pos[0] + v0_unit_vec[0] * slide_dist car_y = lost_grip_pos[1] + v0_unit_vec[1] * slide_dist return car_x, car_y  이 코드가 실제 화면에서 어떻게 애니메이션으로 구동되는지, 그리고 현업 데이터 분석 환경에서는 이론적인 수식과 어떤 간극이 존재하는지에 대한 구체적인 작동 원리와 자세한 설명은 아래 유튜브 콘텐츠를 참고해 주시면 감사하겠습니다. https://youtu.be/tZ1Vvy1g0Yg 

AI 코딩Python파이썬데이터시각화MatplotlibNumPy시뮬레이션F1모터스포츠

asdzxc566

링크만 저장하지 말고, 읽기 좋게 정리해두고 싶어서 앱을 만들었습니다!

저는 좋은 글을 보면 일단 저장부터 하게 되는데, 시간이 지나면 “어디에 저장했더라?” 기억이 나지 않은 경우가 많았습니다. 그래서 저장으로 끝나지 않고, 실제로 읽고 다시 꺼내보기까지 이어질 수 있도록 앱을 하나 만들게 됐습니다.​이 앱은 처음부터 아이폰–맥–아이패드를 함께 쓸 수 있도록 설계하여 만들었습니다. 아이폰에서 검색하다가 괜찮은 글을 발견하면 저장해두고, 집에 와서 맥북으로 다시 읽거나 정리하고, 시간이 있을 때 아이패드로 이어서 보는 식으로요. 글을 발견하는 순간과 제대로 읽는 순간이 늘 같지는 않아서, 기기가 바뀌어도 끊기지 않게 동기화를 기본으로 두었습니다.​기본적인 기능으로 저장해둔 글이 쌓여도 정리가 유지되도록 분류 기능을 넣었고, 읽으면서 중요한 문장은 하이라이트로 표시할 수 있게 했습니다. 길게 정리하지 않아도 표시해둔 문장 몇 개만 있으면 나중에 다시 봤을 때 내용이 훨씬 빨리 떠오르더라고요. 여기에 더해, 읽다가 든 생각을 짧게 남길 수 있는 메모 기능도 빠른 시일 안에 추가할 예정입니다.​읽는 환경도 신경 썼습니다. 저는 다크모드를 거의 항상 쓰고, 웹 브라우저도 강제 다크모드를 켤 정도로 다크모드를 선호합니다. 그래서 테마(다크/라이트/그레이,세이지/ 스카이블루 등) 선택과 폰트 크기 조절을 지원하도록 만들었습니다. 추후에는 줄간격과 문단 간격도 조절할 수 있게 준비 중입니다. 작은 설정이지만, 내 눈에 편한 상태가 되면 글을 더 자주, 더 오래 보게 된다는 걸 많이 느꼈습니다.​아티클을 자주 저장하는데 다시 찾기 어려웠던 분, 읽은 내용에 표시를 남겨두고 나중에 다시 확인하고 싶은 분, 그리고 아이폰/맥북/아이패드를 함께 쓰는 분이라면 한 번 사용해보셔도 좋겠습니다. 써보시고 좋았던 점이나 개선되면 좋을 점을 알려주시면, 계속 다듬어가겠습니다!https://apps.apple.com/us/app/stackbox-%ED%9D%A9%EC%96%B4%EC%A7%84-%EC%BD%98%ED%85%90%EC%B8%A0%EB%A5%BC-%ED%95%9C%EA%B3%B3%EC%97%90/id6758572761

아티클rerad-it-later링크저장저장컨텐츠저장컨텐츠개발ios

Spring Boot 2.7 MVC 환경에서 프록시 게이트웨이 구축기

레거시 서버에 투명 프록시를 얹어 신규 서비스로 트래픽을 넘기기까지의 여정 — 기술 선정, 구현, 그리고 삽질의 기록본 글에 등장하는 서비스명, 도메인, 테이블/칼럼명 등은 보안상의 이유로 익명화하여 작성하였습니다. 구현 패턴과 문제 해결 과정은 실제 경험을 그대로 반영하고 있습니다. 1. 배경: 왜 프록시가 필요했는가우리 팀은 기존 레거시 서버에서 신규 서비스로 점진적으로 마이그레이션하는 작업을 진행하고 있었다. 흔히 말하는 Strangler Fig 패턴 — 레거시를 한 번에 걷어내는 게 아니라, 새로운 서비스를 나란히 세워두고 트래픽을 조금씩 옮기면서 레거시를 서서히 "교살"하는 전략이다.여기서 핵심 제약이 하나 있었다. 인증(Authentication)은 레거시 서버가 전담한다는 것이다. 클라이언트의 모든 요청은 반드시 레거시 서버를 먼저 거쳐야 했고, 레거시에서 인증을 완료한 뒤에야 신규 서비스로 넘길 수 있었다. 단순히 nginx나 로드밸런서 레벨에서 라우팅을 바꾸는 것으로는 해결이 안 되는 구조였다.그래서 레거시 서버 안에 프록시 레이어를 만들기로 했다. 인증을 마친 요청에 사용자 컨텍스트(x-user-id, x-request-service 등)를 HTTP 헤더에 실어서 신규 서비스로 투명하게 포워딩하는 구조다.2. 기술 선정: 생각보다 간단하지 않았던 선택처음 떠올린 것: Spring Cloud Gateway프록시라고 하면 가장 먼저 떠오르는 건 Spring Cloud Gateway다. 라우팅, 필터, 서킷 브레이커까지 다 갖춰져 있으니 이걸 쓰면 되겠다 싶었다.그런데 문제가 있었다. 우리는 게이트웨이를 별도 서버로 띄울 계획이 아니었다. 레거시 서버 안에 모듈 형태로 넣어서, 레거시가 인증 처리 후 바로 프록시까지 수행하는 구조를 원했다. 그런데 Spring Cloud Gateway는 Reactive 기반, WebFlux(Netty) 위에서 돌아간다. 우리 레거시 서버는 Spring Boot 2.7 + Spring MVC(Tomcat) 환경이다.Servlet 기반 애플리케이션에 Reactive 기반 게이트웨이를 같이 올리는 건 불가능하다. WebServerFactory가 충돌하고, ReactiveWebApplicationContext와 ServletWebApplicationContext는 공존할 수 없다. 별도 마이크로서비스로 분리해서 앞단에 배치하는 구조라면 가능하겠지만, 그건 우리가 원하는 "레거시 안에서 인증 후 바로 포워딩"이라는 요구사항과 맞지 않았다.대안 탐색: OpenFeign vs RestTemplate vs ProxyExchange여기서 세 가지 선택지를 놓고 고민했다.Option A: Spring Cloud OpenFeign신규 서비스의 각 API에 대응하는 Feign Client 인터페이스를 레거시에 선언하고, Controller에서 이를 호출하는 방식이다. 타입 안정성도 좋고, 서킷 브레이커와도 잘 붙는다. 하지만 치명적인 단점이 있었다. 단순 패스스루(pass-through) API조차 레거시에 Controller 메서드와 Feign 메서드를 중복으로 만들어야 한다. API가 100개면 100개의 껍데기 코드가 필요하다. 마이그레이션이 진행될수록 레거시가 줄어드는 게 아니라 오히려 비대해지는 모순이 발생한다.Option B: RestTemplate (수동 프록시)프로젝트에서 외부 API 호출에 이미 쓰고 있는 RestTemplate으로 직접 프록시를 구현하는 방법이다. 익숙한 도구이긴 하지만, 요청/응답 바디를 직접 읽고 다시 써야 하고, 헤더 복사, 에러 핸들링, 바이너리 응답 처리 등을 전부 수동으로 구현해야 한다. 단순 패스스루를 위해 작성해야 할 보일러플레이트 코드가 상당하다.Option C: Spring Cloud Gateway MVC의 ProxyExchange서치를 하다가 발견한 것이 spring-cloud-gateway-mvc였다. Spring Cloud Gateway의 서블릿(MVC) 버전으로, ProxyExchange라는 유틸리티를 제공한다. 요청 본문(body)을 파싱하지 않고 그대로 포워딩하는 투명 프록시를 간단하게 구현할 수 있다. DTO 매핑이나 Controller 추가 없이, 와일드카드 기반으로 수십 개의 API를 한 번에 라우팅할 수 있다.결론: ProxyExchange결국 ProxyExchange를 선택했다. Strangler Fig 패턴의 본질은 "레거시를 껍데기(라우터)로 만들고 서서히 죽이는 것"이다. 단순 전달만 하는 API를 위해 Feign Client와 DTO를 계속 추가하거나, RestTemplate으로 보일러플레이트를 양산하는 건 기술 부채를 늘리는 안티 패턴이다. ProxyExchange로 깔끔하게 투명 프록시를 구현하는 것이 가장 합리적인 선택이었다.3. 구현: 프록시 게이트웨이 공통 모듈모듈 구조레거시 서버의 여러 서브 모듈에서 공통으로 쓸 수 있도록, 프록시 게이트웨이 공통 모듈을 만들었다.utils/proxy-gateway/ ├── build.gradle ├── src/main/java/.../ │ └── ProxyService.java # 핵심 프록시 서비스 └── src/main/resources/ └── proxy-gateway.yml # 환경별 대상 서버 URL 설정 의존성api 'org.springframework.cloud:spring-cloud-gateway-mvc' implementation project(path: ':domain') compileOnly 'org.springframework.boot:spring-boot-starter-security' spring-cloud-gateway-mvc가 핵심이다. WebFlux가 아닌 Servlet 기반이므로, 기존 Spring MVC 환경과 전혀 충돌하지 않는다.ProxyService — 헤더 주입의 핵심프록시의 가장 중요한 역할은 인증 정보와 사용자 컨텍스트를 헤더에 실어 보내는 것이다. ProxyService.withHeaders()가 이 역할을 담당한다.public ProxyExchange<byte[]> withHeaders(ProxyExchange<byte[]> proxy) { proxy.header("x-request-service", serviceName); setUserHeader(getCurrentUserId(), proxy); String traceId = MDC.get("trace_id"); if (traceId != null) { proxy.header("x-trace-id", traceId); } String clientIp = MDC.get("client_ip"); if (clientIp != null) { proxy.header("x-client-ip", clientIp); } forwardClientHeaders(proxy); return proxy; } withHeaders()가 하는 일은 크게 세 가지다. 첫째, x-request-service 헤더에 서비스명을 주입한다. 이 값은 application.yml의 service-name 설정에서 가져온다.둘째, setUserHeader()를 통해 사용자 관련 헤더를 주입한다. Spring Security의 SecurityContextHolder에서 인증된 사용자 ID를 꺼내고, 그 ID로 DB에서 소속 정보를 조회해서 x-user-id, x-user-company-id 등의 헤더를 세팅한다. 이때 null 체크와 Long 파싱 가능 여부를 꼼꼼하게 검증한다 — Spring Security가 classpath에 없는 모듈에서도 에러 없이 동작해야 하기 때문이다.private void setUserHeader(String userId, ProxyExchange<byte[]> proxy) { if (userId == null || userId.isEmpty() || !canParseLong(userId)) { return; } proxy.header("x-user-id", userId); memberRepository.findMemberProxyById(Long.parseLong(userId)).ifPresent(member -> { if (member.getCompanyId() != null) { proxy.header("x-user-company-id", String.valueOf(member.getCompanyId())); } if (member.getOrganizationId() != null) { proxy.header("x-organization-id", String.valueOf(member.getOrganizationId())); } }); } 소속 정보 관련 헤더는 사용자의 역할에 따라 값이 달라진다. 이 분기 로직은 Repository의 네이티브 쿼리에서 CASE WHEN으로 처리하여, 서비스 레이어에서는 단순히 결과를 헤더에 넣기만 하면 된다.select m.id, m.company_id as companyId, case c.type when 'ADMIN' then c.id else c.parent_id end as organizationId from members m left join companies c on m.company_id = c.id and c.deleted_at is null where m.id = :id and m.deleted_at is null limit 1 셋째, forwardClientHeaders()로 클라이언트의 원본 헤더 중 필요한 것들을 패스스루한다. 여기서 authorization 헤더는 x-authorization이라는 별도 키로 변환해서 전달하는데, 이유는 뒤의 삽질 기록에서 다룬다.private void forwardClientHeaders(ProxyExchange<byte[]> proxy) { ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attrs == null) { return; } HttpServletRequest request = attrs.getRequest(); for (String headerName : FORWARDED_HEADERS) { String value = request.getHeader(headerName); if (value != null) { proxy.header(headerName, value); } } String authorization = request.getHeader("authorization"); proxy.header("x-authorization", authorization != null ? authorization : ""); } Controller에서의 사용프록시 Controller는 /** 와일드카드로 모든 하위 경로를 잡아서 HTTP 메서드별로 포워딩한다. 개별 endpoint를 일일이 매핑하는 게 아니라, 경로 전체를 통째로 넘기는 구조다.환경별 설정대상 서버 URL은 설정 파일에서 프로필별로 관리한다.target: base-url: http://localhost:8090 --- spring.config.activate.on-profile: develop target: base-url: https://api-dev.example.com --- spring.config.activate.on-profile: staging target: base-url: https://api-stage.example.com --- spring.config.activate.on-profile: prod target: base-url: https://api.example.com 각 서브 모듈에서는 이 설정 파일을 import만 하면 된다.spring: config: import: - classpath:proxy-gateway.yml 4. 삽질의 기록구현 자체는 깔끔했지만, 실제로 동작시키는 과정에서 세 가지 문제를 만났다. 하나같이 "문서에는 안 나와 있고, 직접 겪어봐야 아는" 류의 문제들이었다.삽질 1: x-authorization 헤더가 null로 들어온다증상: 레거시 서버에서 x-authorization 헤더에 값을 분명히 넣어서 보내는데, 신규 서비스 쪽에서는 x-authorization: null로 들어왔다. 아무리 값을 추가해도 null. 디버깅을 해봐도 레거시 쪽에서는 분명히 값이 세팅되어 있었다.원인: Spring Cloud Gateway MVC는 기본적으로 Authorization, Cookie 등 민감한 헤더(sensitive headers)를 자동으로 제거한다. 프록시 대상 서버로 전달하기 전에 필터링해버리는 것이다. x-authorization이라는 커스텀 헤더명을 썼지만, 내부적으로 Authorization 패턴에 매칭되어 함께 제거된 것이었다.해결: 설정 파일에 sensitive 헤더 목록을 빈 배열로 지정하여 해결했다.spring: cloud: gateway: proxy: sensitive: [] 이 설정은 "어떤 헤더도 민감 헤더로 취급하지 않겠다"는 의미다. 이렇게 하면 클라이언트의 모든 헤더가 대상 서버로 그대로 전달된다.설정 동작기본값(미설정)Authorization, Cookie 등 민감 헤더 자동 제거sensitive: []모든 헤더 통과sensitive: [Cookie]Cookie만 제거, 나머지 통과이 문제가 까다로웠던 이유는, 에러가 나지 않는다는 점이다. 헤더가 조용히 제거될 뿐 예외를 던지거나 로그를 남기지 않는다. 레거시 쪽 로그에서는 값이 잘 들어가고, 신규 서비스 쪽에서만 null이 들어오니 문제의 위치를 특정하기가 어려웠다.삽질 2: 로컬은 되는데 dev에서 GET만 502 Bad Gateway증상: 로컬 환경에서는 모든 API가 정상 동작했다. 그런데 dev 환경에 배포하자마자 GET 요청만 502 Bad Gateway가 떴다. POST는 잘 됐다. nginx 에러 로그에는 이런 메시지가 찍혔다.upstream sent invalid chunked response while reading upstream 원인: 이 문제의 근본 원인은 HTTP의 hop-by-hop 헤더 처리에 있었다.신규 서비스가 응답을 보낼 때 Transfer-Encoding: chunked 헤더를 포함한다. ProxyExchange는 이 응답 헤더를 그대로 레거시 서버의 응답에 복사한다. 그런데 레거시 서버의 서블릿 컨테이너(Tomcat)가 자체적으로 또 다시 chunked 인코딩을 적용한다. 결과적으로 이중 chunked 인코딩이 발생하고, 이를 받은 nginx가 "invalid chunked response"라고 판단하여 502를 반환한 것이다.POST가 괜찮았던 이유는 단순하다. 해당 POST API의 응답 body가 void(빈 응답)이었기 때문이다. body가 없으니 chunked 인코딩이 적용될 대상 자체가 없었다.로컬에서 문제가 없었던 이유도 명확하다. 로컬에서는 nginx를 거치지 않고 Tomcat에 직접 접근하기 때문이다. Tomcat이 이중 chunked를 내보내더라도, 브라우저나 Postman 같은 클라이언트는 이를 관대하게 처리한다. 하지만 nginx는 엄격하게 HTTP 스펙을 준수하기 때문에 거부한 것이다.Transfer-Encoding은 HTTP 스펙에서 hop-by-hop 헤더로 분류된다. 즉, 중간 프록시가 전달해서는 안 되는 헤더다. Spring Cloud Gateway(reactive 버전)에서는 RemoveHopByHop Headers Filter가 자동으로 이런 헤더들을 제거해주지만, spring-cloud-gateway-mvc의 ProxyExchange는 단순 프록시 유틸리티라서 이런 필터 체계가 없다.해결: 프록시 응답에서 Transfer-Encoding 헤더를 수동으로 제거하는 메서드를 만들었다.private ResponseEntity<?> stripTransferEncoding(ResponseEntity<?> response) { return ResponseEntity.status(response.getStatusCode()) .headers(headers -> { headers.putAll(response.getHeaders()); headers.remove("Transfer-Encoding"); }) .body(response.getBody()); } 모든 프록시 응답에 이 메서드를 감싸주는 것으로 해결했다. GET, POST, PUT, DELETE 전부 적용했다. 지금은 GET만 문제가 되지만, 향후 body가 있는 다른 메서드에서도 같은 문제가 발생할 수 있기 때문이다.교훈: spring-cloud-gateway-mvc의 ProxyExchange를 쓸 때는, reactive 버전에서 자동으로 해주는 것들(hop-by-hop 헤더 제거 등)을 직접 챙겨야 한다. 편리한 만큼, 내부에서 뭘 안 해주는지를 정확히 알고 있어야 한다.삽질 3: Query String이 사라진다증상: 목록 조회 API에서 페이징이나 필터 파라미터를 넘겼는데, 신규 서비스 쪽에서 query parameter가 전부 null로 들어왔다. 예를 들어 /items?page=0&size=10으로 요청하면, 신규 서비스에서는 page와 size 둘 다 null이었다.원인: ProxyExchange의 proxy.path()는 경로(path)만 반환한다. Query string은 포함하지 않는다. 즉, /items?page=0&size=10으로 요청해도 proxy.path()는 /items만 돌려준다. 우리는 이 path에 대상 서버 base URL을 붙여서 URI를 만들고 있었으므로, query string이 통째로 날아간 것이다."ProxyExchange가 쿼리 파라미터도 알아서 넘겨주겠지"라고 기대했는데, 그렇지 않았다.해결: HttpServletRequest에서 query string을 직접 꺼내서 URI에 붙이도록 수정했다.private String buildUri(ProxyExchange<byte[]> proxy, HttpServletRequest request) { String path = proxy.path().replaceFirst("/old-path", "/api/v1/new-path"); String uri = proxyService.getBaseUrl() + path; String queryString = request.getQueryString(); if (queryString != null) { uri += "?" + queryString; } return uri; } Controller 메서드에 HttpServletRequest를 파라미터로 추가하고, getQueryString()으로 원본 쿼리 스트링을 그대로 가져와서 붙이는 단순한 방법이다. 인코딩 변환 없이 원본을 그대로 넘기므로, 한글이나 특수문자가 포함된 파라미터도 문제없이 전달된다.5. 최종 형태세 번의 삽질을 거쳐 정착한 프록시 Controller의 최종 형태는 이렇다.@Slf4j @RequiredArgsConstructor @RestController @RequestMapping("/items") public class ItemProxyController { private final ProxyService proxyService; @GetMapping("/**") public ResponseEntity<?> proxyGet(ProxyExchange<byte[]> proxy, HttpServletRequest request) { return stripTransferEncoding(proxyService.withHeaders(proxy).uri(buildUri(proxy, request)).get()); } @PostMapping("/**") public ResponseEntity<?> proxyPost(ProxyExchange<byte[]> proxy, HttpServletRequest request) { return stripTransferEncoding(proxyService.withHeaders(proxy).uri(buildUri(proxy, request)).post()); } @PutMapping("/**") public ResponseEntity<?> proxyPut(ProxyExchange<byte[]> proxy, HttpServletRequest request) { return stripTransferEncoding(proxyService.withHeaders(proxy).uri(buildUri(proxy, request)).put()); } @DeleteMapping("/**") public ResponseEntity<?> proxyDelete(ProxyExchange<byte[]> proxy, HttpServletRequest request) { return stripTransferEncoding(proxyService.withHeaders(proxy).uri(buildUri(proxy, request)).delete()); } private ResponseEntity<?> stripTransferEncoding(ResponseEntity<?> response) { return ResponseEntity.status(response.getStatusCode()) .headers(headers -> { headers.putAll(response.getHeaders()); headers.remove("Transfer-Encoding"); }) .body(response.getBody()); } private String buildUri(ProxyExchange<byte[]> proxy, HttpServletRequest request) { String path = proxy.path().replaceFirst("/items", "/api/v1/items"); String uri = proxyService.getBaseUrl() + path; String queryString = request.getQueryString(); if (queryString != null) { uri += "?" + queryString; } return uri; } } 새로운 API를 신규 서비스로 프록시하고 싶다면, 이 패턴을 그대로 복사해서 path 변환 규칙만 바꾸면 된다. 비즈니스 로직은 전부 신규 서비스에 있고, 레거시는 인증과 라우팅만 담당한다.전달되는 헤더 전체 목록헤더 설명 소스x-user-id인증된 사용자 IDSpring Security principalx-user-company-id사용자 소속 조직 IDRepository 조회x-organization-id상위 조직 IDRepository 조회 (역할에 따라 분기)x-authorization원본 인증 토큰클라이언트 Authorization 헤더x-request-service요청 서비스명service-name 설정값x-trace-id분산 추적 IDMDCx-client-ip클라이언트 IPMDC추가로 클라이언트의 다음 헤더도 그대로 전달된다:accept, accept-encoding, accept-language, content-type, x-accept-language, x-requested-with, x-time-zone6. 회고: 배운 것들spring-cloud-gateway-mvc는 "라이트" 버전이다Spring Cloud Gateway(reactive)의 풍부한 필터 체인, hop-by-hop 헤더 자동 제거, 자동 쿼리 파라미터 전달 — 이런 것들을 기대하면 안 된다. ProxyExchange는 말 그대로 "프록시 유틸리티"일 뿐이다. 편리하지만, 내부에서 뭘 안 해주는지를 정확히 알고 보완해야 한다."로컬에서 됩니다"를 믿지 마라이번 Transfer-Encoding 이슈는 로컬에서는 절대 재현되지 않는 문제였다. 로컬은 nginx를 거치지 않고, 브라우저와 Postman은 잘못된 chunked 응답도 관대하게 처리하기 때문이다. 프록시 구현 후에는 실제 배포 환경(nginx → Tomcat 구조)에서 반드시 검증해야 한다.투명 프록시의 가치OpenFeign이었다면 API 하나를 넘길 때마다 Controller, Feign Client, Request DTO, Response DTO를 전부 만들어야 했을 것이다. ProxyExchange 덕분에 레거시에는 얇은 라우팅 코드만 남기고, 실제 비즈니스 로직은 신규 서비스에서 깔끔하게 관리할 수 있게 되었다. Strangler Fig 패턴의 취지에 가장 부합하는 선택이었다고 생각한다.삽질도 자산이다sensitive 헤더 설정, hop-by-hop 헤더 처리, query string 수동 전달 — 이 세 가지는 공식 문서에서 찾기 어려운 실전 지식이다. 특히 gateway-mvc는 reactive 버전에 비해 레퍼런스가 적다. 이런 경험들을 정리해두면 팀 내 다른 개발자들이 같은 삽질을 반복하지 않을 수 있다.

SpringBootgatewayproxymvc

Go Hard

Streamlit과 Plotly를 활용한 차계부 개발

안녕하세요. 파이썬과 Streamlit을 활용하여 차량 주행 기록과 유지비를 시각화하는 '드라이빙 대시보드' 토이 프로젝트를 진행했습니다.이번 프로젝트를 구현하면서 가장 까다로웠던 부분 중 하나는, Plotly 라이브러리의 기본 인터랙티브 기능(드래그, 줌)이 모바일이나 웹 대시보드 환경에서 오히려 사용성을 크게 해친다는 점이었습니다. 마우스 휠이나 스크롤 시 그래프가 의도치 않게 날아가 버리는 현상을 제어하기 위해, 아래와 같이 Plotly의 레이아웃 속성을 고정하는 방식을 적용했습니다. Python# Plotly 그래프 줌/드래그 원천 차단 및 레이아웃 고정 핵심 코드fig_eff.update_layout(title="연비 트렌드 및 차량 이슈 (화면 고정)",hovermode='closest',dragmode=False, # 드래그 차단xaxis=dict(tickformat="%Y년 %m월 %d일", fixedrange=True), # X축 줌 차단yaxis=dict(fixedrange=True) # Y축 줌 차단)# config 설정을 통해 상단 메뉴바 숨김 처리st.plotly_chart(fig_eff, use_container_width=True, config={'displayModeBar': False}) 단순한 데이터 입출력뿐만 아니라, Streamlit의 session_state를 활용한 상태 관리와 Supabase 연동 로직도 함께 포함되어 있습니다. 구체적인 컴포넌트 간의 작동 원리나 전체 아키텍처, 그리고 상세한 소스 코드는 아래 영상과 깃허브에 정리해 두었습니다.데이터 시각화 대시보드 구축에 관심 있으신 분들께 도움이 되기를 바랍니다.🔗 GitHub 소스 코드: https://github.com/gohard-lab/driving_dashboard▶ 구체적인 작동 원리 및 개발 과정 : https://youtu.be/uVhkTbIp7rg 

차계부차량관리자동차유지비연비전비데이터분석파이썬코딩스트림릿Python

제어쟁이

자동차 SW 표준 AUTOSAR, 핵심만 정리했습니다

자동차 SW 표준 AUTOSAR, 핵심만 정리했습니다 자동차 한 대에 ECU가 100개 이상 들어간다는 사실, 알고 계셨나요? 엔진 제어, 변속기, 브레이크, ADAS까지 — 현대의 자동차는 사실상 100개의 독립적인 임베디드 시스템이 네트워크로 연결된 거대한 복합체입니다.공급사가 바뀌면 전부 다시?문제는 여기서 시작됩니다. OEM이 인버터를 만들 공급사를 선정하면, 그 공급사의 하드웨어와 소프트웨어 아키텍처에 맞춰 개발해야 합니다. 그런데 공급사가 바뀌면? MCU가 단종되면? 차종이 바뀌면? 매번 소프트웨어를 처음부터 다시 작성해야 했습니다.이 비효율을 해결하기 위해 2003년, BMW·보쉬·컨티넨탈·다임러 등 경쟁사들이 한자리에 모여 만든 표준이 바로 AUTOSAR입니다.AUTOSAR의 핵심 — 추상화와 레이어 구조AUTOSAR의 핵심 아이디어는 단 한 단어, 추상화입니다. 소프트웨어와 하드웨어 사이에 추상화 계층을 두어, MCU가 바뀌어도 애플리케이션 코드를 한 줄도 수정할 필요가 없게 만드는 것입니다.이를 위해 AUTOSAR는 소프트웨어를 4개의 레이어로 나눕니다. 맨 아래 MCU(하드웨어), 그 위에 BSW(기본 소프트웨어), 중간에 RTE(실행 환경), 맨 위에 ASW(애플리케이션)가 올라갑니다. BSW는 다시 MCAL, ECU Abstraction, Service Layer로 세분화되어 하드웨어의 차이를 흡수합니다.SWC 모듈화와 개발 워크플로우애플리케이션은 SWC(Software Component) 단위로 개발합니다. 레고 블록처럼 모터 제어, 배터리 관리, 고장 진단 등 기능별로 모듈화하고, 표준화된 포트와 인터페이스로 연결합니다. 한번 검증된 SWC는 다른 차종에서도 그대로 재사용할 수 있습니다.실제 개발은 설계 → ARXML 설정 → 코드 생성 → 통합의 4단계로 진행됩니다. DaVinci, EB tresos 같은 전문 도구로 설계하면 BSW와 RTE 코드가 자동 생성되어, 개발자는 애플리케이션 로직에만 집중할 수 있습니다.왜 AUTOSAR인가?AUTOSAR를 도입하면 세 가지 핵심 가치를 얻습니다. 이식성 — MCU가 바뀌어도 BSW만 교체하면 되므로 재개발 비용이 획기적으로 줄어듭니다. 재사용성 — 검증된 SWC를 여러 차종에 그대로 적용할 수 있습니다. 표준화 — 전 세계 300개 이상의 기업이 같은 아키텍처, 같은 인터페이스를 사용하므로 어디서든 통하는 역량이 됩니다.물론 모든 프로젝트에서 100% 의무는 아닙니다. 하지만 전기차와 자율주행 시대로 가면서 소프트웨어 복잡도가 급격히 증가하고 있고, AUTOSAR 채택 비율은 계속 늘어나고 있습니다. 자동차 SW 개발에 관심이 있다면, 알아두면 확실한 무기가 될 것입니다.영상으로 더 자세히 보기: https://youtu.be/6g2ifSU69wQ?si=9Jpf5N2E0oopYqsx 

임베디드 · IoTAUTOSAR자동차소프트웨어임베디드전기전자모터제어자동차sw

위랩스페이스

[풀스택_인프라] LG CNS AM Inspire Camp 5기 모집

 고용노동부 K-Digital Training 과정LG CNS AM Inspire Camp 5기 모집 중 LG CNS 현직자와 함께하는 실전 프로젝트 과정.과정 설계부터 멘토링, 결과물 완성까지 직접 경험합니다.LG CNS AM 5기에서 실력으로 증명하는 엔지니어로 성장하세요. 차별화된 취업 스펙 쌓으러 가기 ▶ [ Click! ]   LG CNS AM만의 특별한 교육 혜택 📌 혜택01 | LG CNS 공식 수료증- 교육 과정을 완주한 수료생에게 LG CNS 공식 수료증을 수여 📌 혜택02 | LG CNS 채용 가산점 부여- LG CNS 및 자회사 지원 시 우수 수료생 한정 취업 가산점 부여 기회 제공 📌 혜택03 | 훈련장려금 지급- 훈련장려금 총 180만원 이상(월 최대 30만 원) 지급 📌 혜택04 | LG CNS 전문가 멘토링- LG CNS 현업 전문가와 함께하는 실무 프로젝트 멘토링 📌 혜택05 | 취업지원 / 맞춤형 프로그램 지원- 성공적인 취업을 위한 전문가의 채용 특강 및 이력서, 고퀄리티 포트폴리오 컨설팅 진행 📌 혜택06 | 교육용 장비 및 서비스 제공- LG 교육용 노트북 대여 및 클라우드 (AWS) + 메타버스 + AI 서비스 이용료 지원 📌 혜택07 | 차별화된 맞춤 교육 진행- 전공/비전공자 개별 지도- 개인별 강점 기반 포트폴리오 지원   LG CNS AM 과정을 수료하면 이렇게 성장해요! ✨프론트엔드 엔지니어“완성도 높은 인터페이스로 서비스의 첫 인상을 완성해요.”사용자 경험을 고려한 화면(UX/UI)을 설계하고, 반응형 웹 기반으로 구현합니다. ✨백엔드 엔지니어“안정적이고 확장 가능한 데이터 흐름을 설계해요.”서비스 로직을 설계하고, 데이터 처리 및 API 서버를 구축합니다. ✨클라우드 엔지니어“확장성과 안정성을 갖춘 클라우드 환경을 구성해요.”인프라 환경에서 서비스를 설계·배포·운영합니다. ✨데브옵스 엔지니어“개발과 운영을 자동화해 안정적인 서비스를 제공합니다.”CI/CD 파이프라인을 구축하고, 효율적인 배포와 운영 환경을 구현합니다.   LG CNS AM 5기, 지금 모집 중이에요! ✅ 기간 및 일정· 모집 마감 : ~ 04.05(일) 23:59※ 우선 선발 기회 제공(선착순)· 교육 기간 : 26.04.10 (금) ~ 26.10.08 (목) ✅ 수업 방식/장소· 온라인 : 메타버스 강의장· 오프라인 : 동국대 서울캠퍼스 ✅ 모집 대상· 국내외 대학(원) 졸업(예정)자· 내일배움카드 발급 가능자 ✅ 접수 방법[랜딩페이지 접속] → [우측 상단 '지금 지원하기' 클릭] → 지원서 작성/제출 ✅ 수강료· 교육비 자부담금 50만원 (*정책 변경으로 인한 자부담금 발생)· 교육 수료 시 축하금 지급 (50만 원) ✅ 교육 문의· 카카오톡 1:1 문의하기 : https://pf.kakao.com/_wbxkln

kdt국비교육프로젝트부트캠프엔지니어취업lgcns풀스택개발it

위랩스페이스

[풀스택_인프라] LG CNS AM Inspire Camp 5기 모집

 고용노동부 K-Digital Training 과정LG CNS AM Inspire Camp 5기 모집 중 LG CNS 현직자와 함께하는 실전 프로젝트 과정.과정 설계부터 멘토링, 결과물 완성까지 직접 경험합니다.LG CNS AM 5기에서 실력으로 증명하는 엔지니어로 성장하세요. 차별화된 취업 스펙 쌓으러 가기 ▶ [ Click! ]   LG CNS AM만의 특별한 교육 혜택 📌 혜택01 | LG CNS 공식 수료증- 교육 과정을 완주한 수료생에게 LG CNS 공식 수료증을 수여 📌 혜택02 | LG CNS 채용 가산점 부여- LG CNS 및 자회사 지원 시 우수 수료생 한정 취업 가산점 부여 기회 제공 📌 혜택03 | 훈련장려금 지급- 훈련장려금 총 180만원 이상(월 최대 30만 원) 지급 📌 혜택04 | LG CNS 전문가 멘토링- LG CNS 현업 전문가와 함께하는 실무 프로젝트 멘토링 📌 혜택05 | 취업지원 / 맞춤형 프로그램 지원- 성공적인 취업을 위한 전문가의 채용 특강 및 이력서, 고퀄리티 포트폴리오 컨설팅 진행 📌 혜택06 | 교육용 장비 및 서비스 제공- LG 교육용 노트북 대여 및 클라우드 (AWS) + 메타버스 + AI 서비스 이용료 지원 📌 혜택07 | 차별화된 맞춤 교육 진행- 전공/비전공자 개별 지도- 개인별 강점 기반 포트폴리오 지원   LG CNS AM 과정을 수료하면 이렇게 성장해요! ✨프론트엔드 엔지니어“완성도 높은 인터페이스로 서비스의 첫 인상을 완성해요.”사용자 경험을 고려한 화면(UX/UI)을 설계하고, 반응형 웹 기반으로 구현합니다. ✨백엔드 엔지니어“안정적이고 확장 가능한 데이터 흐름을 설계해요.”서비스 로직을 설계하고, 데이터 처리 및 API 서버를 구축합니다. ✨클라우드 엔지니어“확장성과 안정성을 갖춘 클라우드 환경을 구성해요.”인프라 환경에서 서비스를 설계·배포·운영합니다. ✨데브옵스 엔지니어“개발과 운영을 자동화해 안정적인 서비스를 제공합니다.”CI/CD 파이프라인을 구축하고, 효율적인 배포와 운영 환경을 구현합니다.   LG CNS AM 5기, 지금 모집 중이에요! ✅ 기간 및 일정· 모집 마감 : ~ 04.05(일) 23:59※ 우선 선발 기회 제공(선착순)· 교육 기간 : 26.04.10 (금) ~ 26.10.08 (목) ✅ 수업 방식/장소· 온라인 : 메타버스 강의장· 오프라인 : 동국대 서울캠퍼스 ✅ 모집 대상· 국내외 대학(원) 졸업(예정)자· 내일배움카드 발급 가능자 ✅ 접수 방법[랜딩페이지 접속] → [우측 상단 '지금 지원하기' 클릭] → 지원서 작성/제출 ✅ 수강료· 교육비 자부담금 50만원 (*정책 변경으로 인한 자부담금 발생)· 교육 수료 시 축하금 지급 (50만 원) ✅ 교육 문의· 카카오톡 1:1 문의하기 : https://pf.kakao.com/_wbxkln

KDT국비교육프로젝트부트캠프엔지니어취업lgcns풀스택개발it

채널톡 아이콘