Vue.js 환경에서 페이지 이탈 방지 팝업 구현하기
안녕하세요. 에이닷에서 프론트엔드 개발자로 일하고 있는 스카이입니다.오늘은 사용자의 편의성을 위해 이탈 방지 팝업을 서비스에 적용하면서 했던 경험을 정리하여 글로 남깁니다.에이닷 회원 가입 과정에서 사용자가 실수로 페이지를 벗어날 때 입력한 정보가 유실되는 문제가 발견되었습니다.이 문제는 사용자 경험을 저해하는 중요한 이슈로, 이탈 방지 팝업을 통해 해결하고자 했습니다.가끔 사이트에서 아래와 같은 팝업을 마주하게 될때가 있습니다.앞서 보여드린 팝업을 노출 시키는 방법은 매우 간단합니다.beforeunload 이벤트를 추가하면 됩니다. 해당 이벤트는 페이지를 벗어나려고 할때 이벤트가 트리거 됩니다.그런데 이벤트 적용 했을때 다음과 같은 문제점이 있었습니다.페이지에 사용자 제스처(클릭/터치)가 없으면 이벤트는 트리거 되지 않습니다.이전 페이지와 현재 페이지의 도메인 주소가 다를때만 동작합니다.에이닷은 Vue.js 기반의 SPA(Single Page Application)입니다. 라우터를 기반으로 화면이 이동되다보니 상위 도메인 경로가 동일합니다.이럴때는 동작하지 않는 문제가 발견되었습니다.브라우저 고유 confirm만 사용 가능해당 이벤트를 사용하면 오직 각 브라우저에서 제공하는 형태의 confirm이 노출됩니다.이전에는 메세지 내용을 변경할수 있게 제공되었으나 현재는 메세지 내용을 바꿀수가 없습니다.onBeforeRouteLeave 이벤트는 Vue.js에서 사용하는 이벤트로 해당 페이지를 벗어나려고 할때 트리거 됩니다.이벤트 사 다음과 같은 문제점이 있었습니다.SPA 환경에서만 동작당연한 말이지만 라우터를 통하면 페이지 이동의 경우에만 발생합니다.의 발생 조건인 이전 페이와 현재 페이지의 도메인이 다른 경우에는 동작하지 않습니다.이벤트가 이벤트 보다 나은 점은 다음과 같습니다.beforeunload 경우 사용자의 동작이 있어야 이벤트가 트리거 되는 조건이 있었습니다.그러나 onBeforeRouteLeave는 사용자 동작 유무와 무관하게 이벤트가 트리거 됩니다.는 각 브라우저에서 제공하는 confirm만 사용이 가능합니다. 메세지 내용을 수정할수 없습니다.는 문구를 변경할수 있습니다. 또한 async/await 제공하는 confirm UI 만들어서 제공할수도 있습니다.에이닷은 다른 도메인을 통해 이동되는 경우와 라우터를 통해 이동되는 경우 2가지가 혼재 합니다. 그래서 두가지 경우를 모두 커버할수 있도록 구현했습니다.효과적인 이탈 방지 팝업 구현을 위해서는 기술적 제약을 이해하고, 다양한 시나리오를 고려한 유연한 접근이 필요합니다.Vue.js 환경에서는 라우터 이벤트와 브라우저 이벤트를 조합하여 최적의 사용자 경험을 제공할 수 있습니다.두 이벤트가 지원하는 브라우저
vuejs
1/7/2025
Vue.js 환경에서 페이지 이탈 방지 팝업 구현하기
안녕하세요. 에이닷에서 프론트엔드 개발자로 일하고 있는 스카이입니다.오늘은 사용자의 편의성을 위해 이탈 방지 팝업을 서비스에 적용하면서 했던 경험을 정리하여 글로 남깁니다.에이닷 회원 가입 과정에서 사용자가 실수로 페이지를 벗어날 때 입력한 정보가 유실되는 문제가 발견되었습니다.이 문제는 사용자 경험을 저해하는 중요한 이슈로, 이탈 방지 팝업을 통해 해결하고자 했습니다.가끔 사이트에서 아래와 같은 팝업을 마주하게 될때가 있습니다.앞서 보여드린 팝업을 노출 시키는 방법은 매우 간단합니다.beforeunload 이벤트를 추가하면 됩니다. 해당 이벤트는 페이지를 벗어나려고 할때 이벤트가 트리거 됩니다.그런데 이벤트 적용 했을때 다음과 같은 문제점이 있었습니다.페이지에 사용자 제스처(클릭/터치)가 없으면 이벤트는 트리거 되지 않습니다.이전 페이지와 현재 페이지의 도메인 주소가 다를때만 동작합니다.에이닷은 Vue.js 기반의 SPA(Single Page Application)입니다. 라우터를 기반으로 화면이 이동되다보니 상위 도메인 경로가 동일합니다.이럴때는 동작하지 않는 문제가 발견되었습니다.브라우저 고유 confirm만 사용 가능해당 이벤트를 사용하면 오직 각 브라우저에서 제공하는 형태의 confirm이 노출됩니다.이전에는 메세지 내용을 변경할수 있게 제공되었으나 현재는 메세지 내용을 바꿀수가 없습니다.onBeforeRouteLeave 이벤트는 Vue.js에서 사용하는 이벤트로 해당 페이지를 벗어나려고 할때 트리거 됩니다.이벤트 사 다음과 같은 문제점이 있었습니다.SPA 환경에서만 동작당연한 말이지만 라우터를 통하면 페이지 이동의 경우에만 발생합니다.의 발생 조건인 이전 페이와 현재 페이지의 도메인이 다른 경우에는 동작하지 않습니다.이벤트가 이벤트 보다 나은 점은 다음과 같습니다.beforeunload 경우 사용자의 동작이 있어야 이벤트가 트리거 되는 조건이 있었습니다.그러나 onBeforeRouteLeave는 사용자 동작 유무와 무관하게 이벤트가 트리거 됩니다.는 각 브라우저에서 제공하는 confirm만 사용이 가능합니다. 메세지 내용을 수정할수 없습니다.는 문구를 변경할수 있습니다. 또한 async/await 제공하는 confirm UI 만들어서 제공할수도 있습니다.에이닷은 다른 도메인을 통해 이동되는 경우와 라우터를 통해 이동되는 경우 2가지가 혼재 합니다. 그래서 두가지 경우를 모두 커버할수 있도록 구현했습니다.효과적인 이탈 방지 팝업 구현을 위해서는 기술적 제약을 이해하고, 다양한 시나리오를 고려한 유연한 접근이 필요합니다.Vue.js 환경에서는 라우터 이벤트와 브라우저 이벤트를 조합하여 최적의 사용자 경험을 제공할 수 있습니다.두 이벤트가 지원하는 브라우저
2025.01.07
vuejs
좋아요
별로에요
현대자동차그룹 글로벌 차량 가입·개통 시스템 개편하기 (feat. MSA)
안녕하세요. 현대자동차그룹 커넥티드 카 서비스(CCS) 백엔드를 개발하고 있는 김성은 책임입니다.이번 신규 국가에 커넥티드 카 서비스를 전개하며 모놀로식 구조의 가입 개통 시스템을 MSA로 이관하고 개선한 경험을 공유하고자 합니다. 개편 프로젝트를 진행하게 된 배경을 시작으로 기존 시스템이 가지고 있었던 문제점을 기술적으로 어떻게 개선했는지를 중점으로 다뤄보겠습니다.커넥티드 카 서비스(CCS)란?현대자동차그룹은 차량 내 인포테인먼트 시스템과 스마트폰을 활용한 커넥티드 카 서비스를 통해 고객에게 더 편리하고 자유로운 모빌리티 경험을 제공하고 있습니다. 서비스의 주요 기능으로는 원격 제어(시동, 문 잠금/해제, 공조 시스템 등), 실시간 차량 상태 확인, 차량 원격 진단 및 무선 소프트웨어 업데이트(OTA), 그리고 비디오 스트리밍 같은 엔터테인먼트 기능까지 제공합니다.해당 서비스는 현대자동차의 블루링크(Bluelink), 기아의 기아 커넥트(Kia Connect), 제네시스의 마이 제네시스(My Genesis) 앱을 통해 이용할 수 있습니다. 2023년 6월 기준 커넥티드 카 서비스는 전 세계적으로 1,000만 명 이상의 가입자를 확보하며 빠르게 성장해 높은 인기를 얻고 있습니다.개편 배경모놀로식 구조와 MSA 혼재글로벌 CCS는 모놀로식(CCS1.0) 구조로 설계되어 운영되고 있었습니다. MSA 전환 프로젝트를 통해 대부분의 시스템은 MSA(CCS2.0)로 전환되었지만, 가입 개통 시스템은 전환되지 않았고 모놀로식 구조를 유지했습니다. 따라서 CCS2.0에서 CCS1.0 가입 개통 시스템을 통해 서비스를 제공하는 모놀로식과 MSA가 혼재된 구조로 서비스를 제공하고 있었습니다.CCS 구조모놀로식으로부터 가입 개통 시스템 분리 필요성기존 국가에서는 CCS1.0 서비스 이용 차량이 아직 존재했기 때문에 CCS1.0과 CCS2.0 서비스를 모두 제공해야 했습니다. 하지만, 신규 국가는 CCS2.0 지원 차량만 판매될 예정이었기 때문에 더 이상 CCS1.0 서비스를 제공할 필요가 없었습니다. 이에 따라, 가입 개통 시스템을 모놀로식 구조로부터 분리하고 개선하는 프로젝트를 진행하게 되었습니다.신규 국가 CCS 구조기존 시스템 한계가입 개통 시스템 자체적으로도 여러 문제점이 있었습니다. 이전 신규 국가에 서비스를 전개할 때는 기존 코드를 기반으로 개발했기 때문에 시스템의 근본적인 문제를 해결하는데 한계가 있었습니다.기술 부채더 이상 유지 보수 되지 않는 자사 프레임워크로 개발Spring 4.3.22, JAVA 8, Mybatis 기술 사용, 테스트 코드 부재높은 코드 복잡도와 낮은 가독성무분별한 Map, if 문, 체크 예외, try-catch 문 사용클라이언트 요청에 대한 공통 처리가 이뤄지지 않음메소드 길이가 길어 해석하기 어려운 코드 존재비효율적인 형상 관리국가별 특화 로직이 거의 없음에도 국가별로 저장소를 나눠 형상 관리동일한 요구사항에 대해 국가별로 다르게 구현되어 국가 간 코드 차이 증가공통 기능 개발이나 버그 수정 시, 반복 작업 필요높은 데이터베이스 부하동일한 레코드를 중복으로 조회하는 비즈니스 로직 존재변하지 않는 데이터에 대한 캐싱이 이뤄지지 않음개편 과정자사 프레임워크는 더 이상 유지 보수되지 않았기 때문에 보안에 취약했고 다른 최신 버전 라이브러리와 호환성이 떨어졌습니다. 이러한 한계를 극복하기 위해, 이번 프로젝트에서는 자사 프레임워크 대신 SpringBoot 기반으로 시스템을 개편하기로 했습니다.레거시 시스템을 다시 개발하는 일은 적지 않은 리스크를 동반합니다. 기존 시스템과 동일한 기능과 동작을 보장해야 하며, 10년 이상 쌓여온 수많은 정책과 복잡한 비즈니스 로직을 정확히 이해하고 개발하는 과정이 필요했습니다. 그럼에도 불구하고, 기술 부채를 회피하지 않고 개선하는 것이 개발자로서 갖춰야 할 태도라고 생각하기 때문에 과감하게 도전했습니다.아래는 이번 시스템 개편의 주요 과정입니다.1. 가입 개통 시스템 비즈니스 로직 파악비즈니스 로직을 파악하기 위한 지름길은 없습니다. 레거시 코드를 하나하나 분석하며 로직을 이해하였고 궁금한 사항은 사내 위키를 참고하거나, 개발에 참여한 PM 또는 개발자분께 직접 물어보며 파악하였습니다.2. 섀도잉 기법을 기반으로 설계 및 개발기존 코드를 기반으로 개발하는 것이 아닌 새롭게 개발했기 때문에 기존 시스템과 동일하게 동작하는지 파악해야 했습니다. 따라서, 레거시 시스템 API 응답 값과 개편 시스템 API 응답 값을 비교하는 섀도잉 기법을 통해 기존 시스템과 동일하게 동작하는지 확인하며 개발하였습니다.3. 개편 시스템 검증개편한 시스템을 검증하기 위해 많은 테스트 코드를 작성하였지만, 이것만으론 신뢰를 얻긴 어려웠습니다. 이를 보완하기 위해 10회 이상의 차량 단말 테스트를 거쳐 시스템의 신뢰성을 확보했습니다.주요 개편 사항유연하고 확장성 높은 시스템 설계가입 개통 시스템 요구사항은 국가 간 대부분 비슷했지만, 약간의 차이가 있어 국가별로 별도의 코드 저장소에 관리되고 각기 다른 개발자가 맡아 운영해 왔습니다. 이로 인해 동일한 요구사항이 국가마다 다르게 구현되면서 코드의 일관성이 떨어지고 시스템 운영 비용이 증가하는 문제가 있었습니다.위 문제를 해결하기 위해 공통 요구사항은 통일된 로직으로 개발하여 코드 일관성을 높이고 전략 패턴과 의존성 주입(DI)을 통해 국가별 특화 로직이 유연하게 적용될 수 있도록 설계하였습니다. 이렇게 시스템의 확장성을 높여 여러 국가가 하나의 프로젝트로 관리될 수 있게 함으로써 시스템 운영 비용을 줄였습니다.레거시 코드 리팩토링레거시 코드에서는 클라이언트 요청에 대한 공통 관심사가 각 컨트롤러에서 처리되었고 무분별한 체크 예외를 처리하기 위해 try ~ catch 문을 많이 사용했습니다. 또한, 함수 파라미터로 Map을 사용하여 코드 복잡도가 높고 가독성이 떨어지는 문제가 있었습니다.핵심 비즈니스 로직으로부터 공통 관심사 분리위 문제를 해결하기 위해 Interceptor와 AOP를 도입하여 공통 관심사를 분리하였습니다. 예외 처리의 경우 체크 예외 대신 언체크 예외를 사용하고 Controller
java
1/6/2025
현대자동차그룹 글로벌 차량 가입·개통 시스템 개편하기 (feat. MSA)
안녕하세요. 현대자동차그룹 커넥티드 카 서비스(CCS) 백엔드를 개발하고 있는 김성은 책임입니다.이번 신규 국가에 커넥티드 카 서비스를 전개하며 모놀로식 구조의 가입 개통 시스템을 MSA로 이관하고 개선한 경험을 공유하고자 합니다. 개편 프로젝트를 진행하게 된 배경을 시작으로 기존 시스템이 가지고 있었던 문제점을 기술적으로 어떻게 개선했는지를 중점으로 다뤄보겠습니다.커넥티드 카 서비스(CCS)란?현대자동차그룹은 차량 내 인포테인먼트 시스템과 스마트폰을 활용한 커넥티드 카 서비스를 통해 고객에게 더 편리하고 자유로운 모빌리티 경험을 제공하고 있습니다. 서비스의 주요 기능으로는 원격 제어(시동, 문 잠금/해제, 공조 시스템 등), 실시간 차량 상태 확인, 차량 원격 진단 및 무선 소프트웨어 업데이트(OTA), 그리고 비디오 스트리밍 같은 엔터테인먼트 기능까지 제공합니다.해당 서비스는 현대자동차의 블루링크(Bluelink), 기아의 기아 커넥트(Kia Connect), 제네시스의 마이 제네시스(My Genesis) 앱을 통해 이용할 수 있습니다. 2023년 6월 기준 커넥티드 카 서비스는 전 세계적으로 1,000만 명 이상의 가입자를 확보하며 빠르게 성장해 높은 인기를 얻고 있습니다.개편 배경모놀로식 구조와 MSA 혼재글로벌 CCS는 모놀로식(CCS1.0) 구조로 설계되어 운영되고 있었습니다. MSA 전환 프로젝트를 통해 대부분의 시스템은 MSA(CCS2.0)로 전환되었지만, 가입 개통 시스템은 전환되지 않았고 모놀로식 구조를 유지했습니다. 따라서 CCS2.0에서 CCS1.0 가입 개통 시스템을 통해 서비스를 제공하는 모놀로식과 MSA가 혼재된 구조로 서비스를 제공하고 있었습니다.CCS 구조모놀로식으로부터 가입 개통 시스템 분리 필요성기존 국가에서는 CCS1.0 서비스 이용 차량이 아직 존재했기 때문에 CCS1.0과 CCS2.0 서비스를 모두 제공해야 했습니다. 하지만, 신규 국가는 CCS2.0 지원 차량만 판매될 예정이었기 때문에 더 이상 CCS1.0 서비스를 제공할 필요가 없었습니다. 이에 따라, 가입 개통 시스템을 모놀로식 구조로부터 분리하고 개선하는 프로젝트를 진행하게 되었습니다.신규 국가 CCS 구조기존 시스템 한계가입 개통 시스템 자체적으로도 여러 문제점이 있었습니다. 이전 신규 국가에 서비스를 전개할 때는 기존 코드를 기반으로 개발했기 때문에 시스템의 근본적인 문제를 해결하는데 한계가 있었습니다.기술 부채더 이상 유지 보수 되지 않는 자사 프레임워크로 개발Spring 4.3.22, JAVA 8, Mybatis 기술 사용, 테스트 코드 부재높은 코드 복잡도와 낮은 가독성무분별한 Map, if 문, 체크 예외, try-catch 문 사용클라이언트 요청에 대한 공통 처리가 이뤄지지 않음메소드 길이가 길어 해석하기 어려운 코드 존재비효율적인 형상 관리국가별 특화 로직이 거의 없음에도 국가별로 저장소를 나눠 형상 관리동일한 요구사항에 대해 국가별로 다르게 구현되어 국가 간 코드 차이 증가공통 기능 개발이나 버그 수정 시, 반복 작업 필요높은 데이터베이스 부하동일한 레코드를 중복으로 조회하는 비즈니스 로직 존재변하지 않는 데이터에 대한 캐싱이 이뤄지지 않음개편 과정자사 프레임워크는 더 이상 유지 보수되지 않았기 때문에 보안에 취약했고 다른 최신 버전 라이브러리와 호환성이 떨어졌습니다. 이러한 한계를 극복하기 위해, 이번 프로젝트에서는 자사 프레임워크 대신 SpringBoot 기반으로 시스템을 개편하기로 했습니다.레거시 시스템을 다시 개발하는 일은 적지 않은 리스크를 동반합니다. 기존 시스템과 동일한 기능과 동작을 보장해야 하며, 10년 이상 쌓여온 수많은 정책과 복잡한 비즈니스 로직을 정확히 이해하고 개발하는 과정이 필요했습니다. 그럼에도 불구하고, 기술 부채를 회피하지 않고 개선하는 것이 개발자로서 갖춰야 할 태도라고 생각하기 때문에 과감하게 도전했습니다.아래는 이번 시스템 개편의 주요 과정입니다.1. 가입 개통 시스템 비즈니스 로직 파악비즈니스 로직을 파악하기 위한 지름길은 없습니다. 레거시 코드를 하나하나 분석하며 로직을 이해하였고 궁금한 사항은 사내 위키를 참고하거나, 개발에 참여한 PM 또는 개발자분께 직접 물어보며 파악하였습니다.2. 섀도잉 기법을 기반으로 설계 및 개발기존 코드를 기반으로 개발하는 것이 아닌 새롭게 개발했기 때문에 기존 시스템과 동일하게 동작하는지 파악해야 했습니다. 따라서, 레거시 시스템 API 응답 값과 개편 시스템 API 응답 값을 비교하는 섀도잉 기법을 통해 기존 시스템과 동일하게 동작하는지 확인하며 개발하였습니다.3. 개편 시스템 검증개편한 시스템을 검증하기 위해 많은 테스트 코드를 작성하였지만, 이것만으론 신뢰를 얻긴 어려웠습니다. 이를 보완하기 위해 10회 이상의 차량 단말 테스트를 거쳐 시스템의 신뢰성을 확보했습니다.주요 개편 사항유연하고 확장성 높은 시스템 설계가입 개통 시스템 요구사항은 국가 간 대부분 비슷했지만, 약간의 차이가 있어 국가별로 별도의 코드 저장소에 관리되고 각기 다른 개발자가 맡아 운영해 왔습니다. 이로 인해 동일한 요구사항이 국가마다 다르게 구현되면서 코드의 일관성이 떨어지고 시스템 운영 비용이 증가하는 문제가 있었습니다.위 문제를 해결하기 위해 공통 요구사항은 통일된 로직으로 개발하여 코드 일관성을 높이고 전략 패턴과 의존성 주입(DI)을 통해 국가별 특화 로직이 유연하게 적용될 수 있도록 설계하였습니다. 이렇게 시스템의 확장성을 높여 여러 국가가 하나의 프로젝트로 관리될 수 있게 함으로써 시스템 운영 비용을 줄였습니다.레거시 코드 리팩토링레거시 코드에서는 클라이언트 요청에 대한 공통 관심사가 각 컨트롤러에서 처리되었고 무분별한 체크 예외를 처리하기 위해 try ~ catch 문을 많이 사용했습니다. 또한, 함수 파라미터로 Map을 사용하여 코드 복잡도가 높고 가독성이 떨어지는 문제가 있었습니다.핵심 비즈니스 로직으로부터 공통 관심사 분리위 문제를 해결하기 위해 Interceptor와 AOP를 도입하여 공통 관심사를 분리하였습니다. 예외 처리의 경우 체크 예외 대신 언체크 예외를 사용하고 Controller
2025.01.06
java
좋아요
별로에요
코틀린 코루틴 예외 처리, 어떻게 해야 할까?
cdragon.cd 코드를 작성할 때 성공 케이스에 대한 로직만큼이나 실패 케이스에 대한 안전한 예외처리는 필수입니다. 구조화된 동시성을 지원하는 코틀린 코루틴에서는 어떻게 예외를 핸들링할 수 있을까요? 반복적으로 try-catch를 작성하고 있었다면 제이코의 글을 통해 더 유려한 예외처리로 리팩토링 해보세요!greg.ss 코틀린 코루틴의 열렬한 지지자, 제이코가 예외 처리에 대해 소개하는 글입니다. 예제와 함께 올바른 방법을 학습하고 자신의 코드를 점검해 보면 어떨까요?안녕하세요, 카카오페이 크레딧클랜에서 대출 플랫폼을 개발하고 있는 제이코입니다. 여러분은 코틀린 코루틴을 사용할 때, 특정 코루틴에서 발생한 예외가 다른 코루틴에 영향을 미쳐 전체 시스템이 불안정해진 경험이 있으신가요? 코루틴은 비동기 프로그래밍의 복잡성을 줄이고 효율적인 동시성을 제공하는 강력한 도구입니다. 하지만 예외 전파와 취소 과정을 제대로 이해하지 못하고 사용하면 전체 시스템이 불안정해질 위험이 있습니다.이번 글에서는 코루틴을 사용할 때 발생할 수 있는 예외로 인한 취소 문제와 이를 해결하기 위한 다양한 방법들을 소개하려고 합니다. 구조화된 동시성 원칙을 중심으로 예외가 어떻게 전파되고, 그로 인해 다른 코루틴에 어떤 영향을 미치는지 구체적인 예제를 통해 살펴볼게요. 이 글이 코루틴을 처음 접하는 분들뿐만 아니라, 실무에서 코루틴을 활용해 안정적이고 신뢰할 수 있는 시스템 설계를 고민하는 모든 분들에게 도움이 되기를 기대합니다.코루틴은 구조화된 동시성 (Structured Concurrency) 원칙을 따릅니다. 이 원칙에 따라 코루틴의 생명 주기와 실행 흐름은 부모-자식 관계로 체계적으로 관리됩니다. 부모 코루틴은 자식 코루틴의 생명 주기를 책임지며, 모든 자식 코루틴이 완료되어야 부모 코루틴도 완료될 수 있습니다. 구조화된 동시성 원칙은 예외 처리에도 중요한 영향을 미칩니다. 자식 코루틴에서 예외가 발생하면 해당 예외는 부모 코루틴으로 전파됩니다. 이를 통해 오류를 체계적으로 관리할 수 있지만, 부모 코루틴이 예외를 적절히 처리하지 못할 경우 전체 코루틴 계층이 취소될 수 있습니다.예외 전파 및 취소의 기본 원리는 다음과 같습니다.• 예외 발생: 코루틴에서 예외가 발생하면 해당 코루틴과 그 하위 코루틴은 모두 취소됩니다.• 예외 전파: 예외는 부모 코루틴으로 전파되며, 부모 코루틴도 취소됩니다.• 전체 계층 취소: 부모 코루틴이 취소되면 다른 자식 코루틴도 모두 취소됩니다.다음 예제를 통해 코루틴의 예외 전파 및 취소 동작을 살펴보겠습니다.실행 결과를 보면, 코루틴 4에서 발생한 예외로 인해 모든 코루틴이 취소된 것을 확인할 수 있습니다.이제 어떤 과정을 거쳐 전체 코루틴이 취소되었는지 살펴볼게요. 아래 그림은 각 코루틴 간의 부모-자식 관계를 나타냅니다.우선, 코루틴 4의 예외 발생으로 인해 코루틴 4가 취소됩니다.코루틴 4의 취소로 인해, 코루틴 4의 자식인 코루틴 5 역시 취소됩니다.코루틴 4에서 발생한 예외는 코루틴 4의 부모 코루틴인 코루틴 1에게
kotlin
1/6/2025
코틀린 코루틴 예외 처리, 어떻게 해야 할까?
cdragon.cd 코드를 작성할 때 성공 케이스에 대한 로직만큼이나 실패 케이스에 대한 안전한 예외처리는 필수입니다. 구조화된 동시성을 지원하는 코틀린 코루틴에서는 어떻게 예외를 핸들링할 수 있을까요? 반복적으로 try-catch를 작성하고 있었다면 제이코의 글을 통해 더 유려한 예외처리로 리팩토링 해보세요!greg.ss 코틀린 코루틴의 열렬한 지지자, 제이코가 예외 처리에 대해 소개하는 글입니다. 예제와 함께 올바른 방법을 학습하고 자신의 코드를 점검해 보면 어떨까요?안녕하세요, 카카오페이 크레딧클랜에서 대출 플랫폼을 개발하고 있는 제이코입니다. 여러분은 코틀린 코루틴을 사용할 때, 특정 코루틴에서 발생한 예외가 다른 코루틴에 영향을 미쳐 전체 시스템이 불안정해진 경험이 있으신가요? 코루틴은 비동기 프로그래밍의 복잡성을 줄이고 효율적인 동시성을 제공하는 강력한 도구입니다. 하지만 예외 전파와 취소 과정을 제대로 이해하지 못하고 사용하면 전체 시스템이 불안정해질 위험이 있습니다.이번 글에서는 코루틴을 사용할 때 발생할 수 있는 예외로 인한 취소 문제와 이를 해결하기 위한 다양한 방법들을 소개하려고 합니다. 구조화된 동시성 원칙을 중심으로 예외가 어떻게 전파되고, 그로 인해 다른 코루틴에 어떤 영향을 미치는지 구체적인 예제를 통해 살펴볼게요. 이 글이 코루틴을 처음 접하는 분들뿐만 아니라, 실무에서 코루틴을 활용해 안정적이고 신뢰할 수 있는 시스템 설계를 고민하는 모든 분들에게 도움이 되기를 기대합니다.코루틴은 구조화된 동시성 (Structured Concurrency) 원칙을 따릅니다. 이 원칙에 따라 코루틴의 생명 주기와 실행 흐름은 부모-자식 관계로 체계적으로 관리됩니다. 부모 코루틴은 자식 코루틴의 생명 주기를 책임지며, 모든 자식 코루틴이 완료되어야 부모 코루틴도 완료될 수 있습니다. 구조화된 동시성 원칙은 예외 처리에도 중요한 영향을 미칩니다. 자식 코루틴에서 예외가 발생하면 해당 예외는 부모 코루틴으로 전파됩니다. 이를 통해 오류를 체계적으로 관리할 수 있지만, 부모 코루틴이 예외를 적절히 처리하지 못할 경우 전체 코루틴 계층이 취소될 수 있습니다.예외 전파 및 취소의 기본 원리는 다음과 같습니다.• 예외 발생: 코루틴에서 예외가 발생하면 해당 코루틴과 그 하위 코루틴은 모두 취소됩니다.• 예외 전파: 예외는 부모 코루틴으로 전파되며, 부모 코루틴도 취소됩니다.• 전체 계층 취소: 부모 코루틴이 취소되면 다른 자식 코루틴도 모두 취소됩니다.다음 예제를 통해 코루틴의 예외 전파 및 취소 동작을 살펴보겠습니다.실행 결과를 보면, 코루틴 4에서 발생한 예외로 인해 모든 코루틴이 취소된 것을 확인할 수 있습니다.이제 어떤 과정을 거쳐 전체 코루틴이 취소되었는지 살펴볼게요. 아래 그림은 각 코루틴 간의 부모-자식 관계를 나타냅니다.우선, 코루틴 4의 예외 발생으로 인해 코루틴 4가 취소됩니다.코루틴 4의 취소로 인해, 코루틴 4의 자식인 코루틴 5 역시 취소됩니다.코루틴 4에서 발생한 예외는 코루틴 4의 부모 코루틴인 코루틴 1에게
2025.01.06
kotlin
좋아요
별로에요
iOS에서 이벤트 기반 URL 요청이 잘 전송되는지 확인하기 (feat. 광고 트래킹.. 제대로 가고 있나요?)
안녕하세요 지마켓 Mobile Application Team 강수진입니다.오늘은 iOS에서 특정 이벤트에 대한 URL 요청이 정상적으로 이루어졌는지 확인하는 방법에 대해 알아보겠습니다.들어가기 전에모든 서비스에서 광고는 중요합니다. 왜냐하면 수익과 직결되기 때문이죠 지마켓도 곳곳에 다양한 유형의 광고가 포함되어 있는데요! 일례로 사용자가 광고 상품을 클릭하면, 해당 이벤트가 광고 처리 시스템으로 전송되어 광고가 집계되고, 이에 따라 비용이 청구될 수 있습니다.요구 사항광고 트래킹은 수익과 직결되기 때문에 문제가 생기면 최우선 순위로 대응해야 하는 이슈 중 하나입니다.그런데 코드 수정을 하다가 기존의 트래킹 코드가 동작을 안 하는 상황이 발생한다면요..?? 심지어 이런 데이터 트래킹의 이슈는 일반적인 QA로 발견되기 어려운데요...? 벌써 아찔하죠..? 그렇다면 배포 전 테스트를 통해 '광고 트래킹 이벤트가 발생했을때, 해당 URL 요청을 제대로 보내고 있는지' 확인할 수 없을까요??이번 프로젝트는 이러한 필요성으로부터 시작됩니다.어떻게 하면 좋을까?앞서 말했듯 '광고 이벤트가 발생하면, 광고 처리 시스템 URL 로 트래킹을 보낸다' 이것이 광고 시스템 처리에 대한 기본 전제입니다.하지만 트리거 이벤트가 발생했을때 진짜로 광고 URL 요청을 보냈는지 확신할 수 있나요? 정말요?? 이 사실을 어떻게 검증할 수 있을까요?기존에 트래킹 이벤트를 아래와 같이 sendTracking 함수에서 처리한다고 할때protocol AdTracker { func sendTracking()}struct AdTrackingClient: AdTracker { func sendTracking() { // URL Request 보냄 }}단순히 Fake 객체를 만들고, sendTracking 함수에서는 isTrackingSent 플래그를 변경하는 방식으로 트래킹이 되었는지 검증하면 될까요?struct FakeAdTrackingClient: AdTracker { var isTrackingSent = false func sendTracking() { isTrackingSent = true }}이와 같은 방식은 sendTracking 함수가 호출되었는지 여부만 확인할 수 있을 뿐, 실제로 URLRequest를 통해 네트워크 요청이 이루어졌는지는 확인할 수 없습니다. 실제 AdTrackingClientAdTrackingClient의 sendTracking 함수에서는 print("hello world")만 구현되어 있을지도 모르는 일이죠!따라서 저희는 실제 URL 요청을 캐치할 수 있는 proxy 를 만들어서 이벤트를 가로채기로 했습니다. 그리고 여기서 가로챈 URL 정보는 저장소에 별도로 저장해 둡니다.광고 트래킹 이벤트를 트리거 시킬 때 URL 요청을 제대로 보낸다면, 해당 URL 정보는 proxy에 의해 저장소에 저장될 겁니다.그럼 우리는 이벤트 트리거 후 URL 이 저장소에 있냐 / 아니냐에 따라서 이벤트에 따른 URL 요청 여부를 판단할 수
1/5/2025
iOS에서 이벤트 기반 URL 요청이 잘 전송되는지 확인하기 (feat. 광고 트래킹.. 제대로 가고 있나요?)
안녕하세요 지마켓 Mobile Application Team 강수진입니다.오늘은 iOS에서 특정 이벤트에 대한 URL 요청이 정상적으로 이루어졌는지 확인하는 방법에 대해 알아보겠습니다.들어가기 전에모든 서비스에서 광고는 중요합니다. 왜냐하면 수익과 직결되기 때문이죠 지마켓도 곳곳에 다양한 유형의 광고가 포함되어 있는데요! 일례로 사용자가 광고 상품을 클릭하면, 해당 이벤트가 광고 처리 시스템으로 전송되어 광고가 집계되고, 이에 따라 비용이 청구될 수 있습니다.요구 사항광고 트래킹은 수익과 직결되기 때문에 문제가 생기면 최우선 순위로 대응해야 하는 이슈 중 하나입니다.그런데 코드 수정을 하다가 기존의 트래킹 코드가 동작을 안 하는 상황이 발생한다면요..?? 심지어 이런 데이터 트래킹의 이슈는 일반적인 QA로 발견되기 어려운데요...? 벌써 아찔하죠..? 그렇다면 배포 전 테스트를 통해 '광고 트래킹 이벤트가 발생했을때, 해당 URL 요청을 제대로 보내고 있는지' 확인할 수 없을까요??이번 프로젝트는 이러한 필요성으로부터 시작됩니다.어떻게 하면 좋을까?앞서 말했듯 '광고 이벤트가 발생하면, 광고 처리 시스템 URL 로 트래킹을 보낸다' 이것이 광고 시스템 처리에 대한 기본 전제입니다.하지만 트리거 이벤트가 발생했을때 진짜로 광고 URL 요청을 보냈는지 확신할 수 있나요? 정말요?? 이 사실을 어떻게 검증할 수 있을까요?기존에 트래킹 이벤트를 아래와 같이 sendTracking 함수에서 처리한다고 할때protocol AdTracker { func sendTracking()}struct AdTrackingClient: AdTracker { func sendTracking() { // URL Request 보냄 }}단순히 Fake 객체를 만들고, sendTracking 함수에서는 isTrackingSent 플래그를 변경하는 방식으로 트래킹이 되었는지 검증하면 될까요?struct FakeAdTrackingClient: AdTracker { var isTrackingSent = false func sendTracking() { isTrackingSent = true }}이와 같은 방식은 sendTracking 함수가 호출되었는지 여부만 확인할 수 있을 뿐, 실제로 URLRequest를 통해 네트워크 요청이 이루어졌는지는 확인할 수 없습니다. 실제 AdTrackingClientAdTrackingClient의 sendTracking 함수에서는 print("hello world")만 구현되어 있을지도 모르는 일이죠!따라서 저희는 실제 URL 요청을 캐치할 수 있는 proxy 를 만들어서 이벤트를 가로채기로 했습니다. 그리고 여기서 가로챈 URL 정보는 저장소에 별도로 저장해 둡니다.광고 트래킹 이벤트를 트리거 시킬 때 URL 요청을 제대로 보낸다면, 해당 URL 정보는 proxy에 의해 저장소에 저장될 겁니다.그럼 우리는 이벤트 트리거 후 URL 이 저장소에 있냐 / 아니냐에 따라서 이벤트에 따른 URL 요청 여부를 판단할 수
2025.01.05
좋아요
별로에요
CI 빌드 오류의 원인 분석에서 해결까지의 여정
LINE Plus의 MPR(Mobile Productive & Research) 팀은 LINE 클라이언트 앱의 빌드 개선과 CI 파이프라인 관리, 자동화 지원 등의 업무를 담당하고 있습니다. 이번 글에서는 저희 팀에서 운영하는 CI/CD에 발생했던 흔하지 않은 이슈와 그 해결 방법을 공유하려고 합니다. 이번 글에서 다룰 이슈는 문제가 발생한 시점부터 다시 정상적으로 CI/CD가 운영되기까지 약 10일 정도의 시간이 소요됐는데요. 추후 유사한 문제가 발생했을 때 어떤 식으로 문제의 원인을 찾고, 분석해서, 해결해 나가야 하는지 그 과정을 참고하고자 작성했습니다. 저희의 문제 해결 과정이 이 글을 읽고 계신 분들께도 도움이 되기를 바라며 시작하겠습니다.현재 MPR 팀에서 운영하고 있는 CI의 전체 구성을 간략하게 소개하겠습니다. 먼저 소스 리포지터리로 Git을 사용하고 있으며, Jenkins와 여러 플러그인을 이용해 CI를 운영하고 있습니다. 빌드 개선 및 오류 검토를 목적으로 Gradle을 활용한 빌드 정보를 Develocity에 수집하고 있으며, Jenkins 빌드 로그를 Logstash를 통해 Elasticsearch로 모아서 집계한 뒤 Kibana 및 Grafana를 사용해 빌드 정보를 시각화하고 있습니다.평소와는 다른 빌드 실패 증상의 발현언제부터인가 빌드 실패 알림이 증가했습니다. CI/CD를 운영하다 보면 예기치 못한 여러 문제가 발생할 수 있으며, 그중 빌드 오류가 발생하는 경우는 다음과 같습니다.• 테스트 관련 오류• 플레이키(flaky) 테스트: 코드는 동일한데 테스트 결과는 다른 경우• 느린 테스트: 테스트 실행하는 데 지나치게 오래 걸리는 경우• 인프라 및 성능 관련 오류• 서버 과부하: 동시에 여러 테스트를 처리하느라 서버나 네트워크에 과부하가 걸리는 경우• 확장성 문제: 개발 팀과 프로젝트의 규모가 커지면서 파이프라인이 증가된 부하를 처리하지 못하지만 구조상 확장하기 어려운 경우• 성능 이상: 서버의 응답이 지연되거나 메모리 누수가 발생하거나 부하 분산이 적절히 처리되지 않은 경우• 통합 및 배포 관련 오류• 도구 간 통합 문제: 내부적으로 인터페이스를 구축해 서로 다른 도구를 통합해서 사용하다가 예상치 못한 도구 간 설정 호환 문제가 발생하는 경우일반적인 경우 빌드 실패의 주요 원인은 유닛 테스트 증가에 따른 빌드 및 테스트 수행 시간의 증가입니다. Jenkins에 빌드 타임아웃 시간을 설정해 놓으면 빌드 및 테스트하는 데 이보다 시간이 오래 걸릴 경우 강제로 중단되는데요. 유닛 테스트가 늘어나 테스트 수행 시간이 증가하거나 일시적으로 자원이 부족해 테스트 수행에 실패하면서 설정해 놓은 빌드 타임아웃 시간을 초과하면 빌드에 실패합니다. 보통 이런 경우는 빌드에 사용할 리소스가 일시적으로 부족하거나 네트워크에 간헐적으로 문제가 생겨 발생하기 때문에 빌드를 다시 실행하면 대부분 재현되지 않습니다.저희는 이번 경우 역시 처음에는 이처럼 시간이 지나면 자연스럽게 해결될 일시적인 증상으로 간주하고 원인을 파악하기 시작했습니
elasticsearch
jenkins
nodejs
1/5/2025
CI 빌드 오류의 원인 분석에서 해결까지의 여정
LINE Plus의 MPR(Mobile Productive & Research) 팀은 LINE 클라이언트 앱의 빌드 개선과 CI 파이프라인 관리, 자동화 지원 등의 업무를 담당하고 있습니다. 이번 글에서는 저희 팀에서 운영하는 CI/CD에 발생했던 흔하지 않은 이슈와 그 해결 방법을 공유하려고 합니다. 이번 글에서 다룰 이슈는 문제가 발생한 시점부터 다시 정상적으로 CI/CD가 운영되기까지 약 10일 정도의 시간이 소요됐는데요. 추후 유사한 문제가 발생했을 때 어떤 식으로 문제의 원인을 찾고, 분석해서, 해결해 나가야 하는지 그 과정을 참고하고자 작성했습니다. 저희의 문제 해결 과정이 이 글을 읽고 계신 분들께도 도움이 되기를 바라며 시작하겠습니다.현재 MPR 팀에서 운영하고 있는 CI의 전체 구성을 간략하게 소개하겠습니다. 먼저 소스 리포지터리로 Git을 사용하고 있으며, Jenkins와 여러 플러그인을 이용해 CI를 운영하고 있습니다. 빌드 개선 및 오류 검토를 목적으로 Gradle을 활용한 빌드 정보를 Develocity에 수집하고 있으며, Jenkins 빌드 로그를 Logstash를 통해 Elasticsearch로 모아서 집계한 뒤 Kibana 및 Grafana를 사용해 빌드 정보를 시각화하고 있습니다.평소와는 다른 빌드 실패 증상의 발현언제부터인가 빌드 실패 알림이 증가했습니다. CI/CD를 운영하다 보면 예기치 못한 여러 문제가 발생할 수 있으며, 그중 빌드 오류가 발생하는 경우는 다음과 같습니다.• 테스트 관련 오류• 플레이키(flaky) 테스트: 코드는 동일한데 테스트 결과는 다른 경우• 느린 테스트: 테스트 실행하는 데 지나치게 오래 걸리는 경우• 인프라 및 성능 관련 오류• 서버 과부하: 동시에 여러 테스트를 처리하느라 서버나 네트워크에 과부하가 걸리는 경우• 확장성 문제: 개발 팀과 프로젝트의 규모가 커지면서 파이프라인이 증가된 부하를 처리하지 못하지만 구조상 확장하기 어려운 경우• 성능 이상: 서버의 응답이 지연되거나 메모리 누수가 발생하거나 부하 분산이 적절히 처리되지 않은 경우• 통합 및 배포 관련 오류• 도구 간 통합 문제: 내부적으로 인터페이스를 구축해 서로 다른 도구를 통합해서 사용하다가 예상치 못한 도구 간 설정 호환 문제가 발생하는 경우일반적인 경우 빌드 실패의 주요 원인은 유닛 테스트 증가에 따른 빌드 및 테스트 수행 시간의 증가입니다. Jenkins에 빌드 타임아웃 시간을 설정해 놓으면 빌드 및 테스트하는 데 이보다 시간이 오래 걸릴 경우 강제로 중단되는데요. 유닛 테스트가 늘어나 테스트 수행 시간이 증가하거나 일시적으로 자원이 부족해 테스트 수행에 실패하면서 설정해 놓은 빌드 타임아웃 시간을 초과하면 빌드에 실패합니다. 보통 이런 경우는 빌드에 사용할 리소스가 일시적으로 부족하거나 네트워크에 간헐적으로 문제가 생겨 발생하기 때문에 빌드를 다시 실행하면 대부분 재현되지 않습니다.저희는 이번 경우 역시 처음에는 이처럼 시간이 지나면 자연스럽게 해결될 일시적인 증상으로 간주하고 원인을 파악하기 시작했습니
2025.01.05
elasticsearch
jenkins
nodejs
좋아요
별로에요
Compose와 MVI로 다시 태어난 Android UI: MVVM에서 MVI로의 전환기
안녕하세요, 여기어때컴퍼니 Android 개발팀의 에이든입니다.최근 검색 화면을 웹에서 네이티브로 전환하는 프로젝트를 진행했습니다. 이 화면은 사용자 상호작용이 많고 데이터 갱신이 빈번하여, 효과적인 상태 관리가 필수적이었습니다. 이를 위해 기존의 MVVM 방식보다 단방향 데이터 흐름과 선언형 UI의 장점을 극대화할 수 있는 MVI 패턴을 선택했습니다. 이 과정에서 Jetpack Compose와 MVI 패턴을 결합하여, 더 직관적이고 효율적인 Android 개발 환경을 구축한 경험을 소개하고자 합니다.왜 MVI를 선택했을까?기존에 널리 쓰이던 MVVM은 안정적이고 익숙한 방식이지만, UI가 복잡하고 상태 변화가 잦은 화면에서는 관리 포인트가 늘어나는 단점이 있습니다.ViewModel에서 여러 상태를 관리하기 위해 다수의 프로퍼티를 생성하다 보면, 이를 View가 각각 관찰하면서 코드의 복잡성이 증가 했습니다.상태 변경과 UI 반영 간의 비동기 문제가 발생하거나, 예상치 못한 UI 갱신 이슈로 인해 디버깅이 어려운 경우가 많았습니다.Compose와 MVI를 도입한 이유Compose와 MVI는 이러한 문제를 해결하기 위해 상호 보완적으로 작동하며, 상태 관리와 UI 동기화를 효율적으로 처리하는 데 장점이 있습니다.Jetpack Compose선언형 UI로 간결하고 가독성 높은 코드를 작성할 수 있습니다.상태 변화에 따라 화면이 자동으로 재구성되어 UI 구현이 용이합니다.MVI 패턴단방향 데이터 흐름을 통해 상태 중심 개발을 가능하게 합니다.복잡한 UI 상태를 예측 가능하고 일관성 있게 관리할 수 있습니다.명확한 상태 전달 구조로 소스 코드 수정 및 디버깅을 더욱 쉽게 만듭니다.Compose와 MVI의 조합 효과MVI는 상태 변화를 명확히 전달하며, Compose는 이를 즉각적으로 UI에 반영합니다. 이로 인해:코드의 가독성과 간결성을 개선할 수 있었고,UI 상태 변화와 동기화 문제를 최소화하며,유지보수성을 높여 보다 효율적인 개발 환경을 구축할 수 있었습니다.MVI 패턴이란?본격적인 전환에 앞서 MVI 패턴에 대해 간단히 알아보겠습니다.MVI 패턴은 단방향 데이터 흐름을 통해 상태 중심의 구현을 할 수 있도록 설계되었습니다. 주요 구성 요소의 역할은 다음과 같습니다:Intent사용자의 액션이나 이벤트를 전달합니다.이벤트가 발생하면 이를 처리한 뒤 결과를 Model로 전달합니다.ModelView가 렌더링 되는 데 필요한 SSOT(Single Source of Truth)입니다.Immutable하며, 모든 상태 변경은 Intent를 통해 새로운 객체를 생성하는 방식으로 이루어집니다.ViewUI를 담당하며 사용자의 액션(Intent)을 전달합니다.Model을 구독하여 상태 변경에 따라 UI를 렌더링합니다.MVI 패턴의 흐름은 직관적입니다. 사용자의 액션(Intent)이 발생하면 Model의 상태가 변경되고, View는 변경된 상태를 기반으로 UI를 새롭게 렌더링합니다.일회성 작업과 Side Effects(부수효과)화면 전환이나 Toast 메시지 표
1/5/2025
Compose와 MVI로 다시 태어난 Android UI: MVVM에서 MVI로의 전환기
안녕하세요, 여기어때컴퍼니 Android 개발팀의 에이든입니다.최근 검색 화면을 웹에서 네이티브로 전환하는 프로젝트를 진행했습니다. 이 화면은 사용자 상호작용이 많고 데이터 갱신이 빈번하여, 효과적인 상태 관리가 필수적이었습니다. 이를 위해 기존의 MVVM 방식보다 단방향 데이터 흐름과 선언형 UI의 장점을 극대화할 수 있는 MVI 패턴을 선택했습니다. 이 과정에서 Jetpack Compose와 MVI 패턴을 결합하여, 더 직관적이고 효율적인 Android 개발 환경을 구축한 경험을 소개하고자 합니다.왜 MVI를 선택했을까?기존에 널리 쓰이던 MVVM은 안정적이고 익숙한 방식이지만, UI가 복잡하고 상태 변화가 잦은 화면에서는 관리 포인트가 늘어나는 단점이 있습니다.ViewModel에서 여러 상태를 관리하기 위해 다수의 프로퍼티를 생성하다 보면, 이를 View가 각각 관찰하면서 코드의 복잡성이 증가 했습니다.상태 변경과 UI 반영 간의 비동기 문제가 발생하거나, 예상치 못한 UI 갱신 이슈로 인해 디버깅이 어려운 경우가 많았습니다.Compose와 MVI를 도입한 이유Compose와 MVI는 이러한 문제를 해결하기 위해 상호 보완적으로 작동하며, 상태 관리와 UI 동기화를 효율적으로 처리하는 데 장점이 있습니다.Jetpack Compose선언형 UI로 간결하고 가독성 높은 코드를 작성할 수 있습니다.상태 변화에 따라 화면이 자동으로 재구성되어 UI 구현이 용이합니다.MVI 패턴단방향 데이터 흐름을 통해 상태 중심 개발을 가능하게 합니다.복잡한 UI 상태를 예측 가능하고 일관성 있게 관리할 수 있습니다.명확한 상태 전달 구조로 소스 코드 수정 및 디버깅을 더욱 쉽게 만듭니다.Compose와 MVI의 조합 효과MVI는 상태 변화를 명확히 전달하며, Compose는 이를 즉각적으로 UI에 반영합니다. 이로 인해:코드의 가독성과 간결성을 개선할 수 있었고,UI 상태 변화와 동기화 문제를 최소화하며,유지보수성을 높여 보다 효율적인 개발 환경을 구축할 수 있었습니다.MVI 패턴이란?본격적인 전환에 앞서 MVI 패턴에 대해 간단히 알아보겠습니다.MVI 패턴은 단방향 데이터 흐름을 통해 상태 중심의 구현을 할 수 있도록 설계되었습니다. 주요 구성 요소의 역할은 다음과 같습니다:Intent사용자의 액션이나 이벤트를 전달합니다.이벤트가 발생하면 이를 처리한 뒤 결과를 Model로 전달합니다.ModelView가 렌더링 되는 데 필요한 SSOT(Single Source of Truth)입니다.Immutable하며, 모든 상태 변경은 Intent를 통해 새로운 객체를 생성하는 방식으로 이루어집니다.ViewUI를 담당하며 사용자의 액션(Intent)을 전달합니다.Model을 구독하여 상태 변경에 따라 UI를 렌더링합니다.MVI 패턴의 흐름은 직관적입니다. 사용자의 액션(Intent)이 발생하면 Model의 상태가 변경되고, View는 변경된 상태를 기반으로 UI를 새롭게 렌더링합니다.일회성 작업과 Side Effects(부수효과)화면 전환이나 Toast 메시지 표
2025.01.05
좋아요
별로에요
Aurora Serverless 알아보기
안녕하세요, SRE팀에 DBA로 새로 합류한 브라우니입니다.AWS Service에 여러 서비스가 있지만 그 중에서도 여러 서비스에 활용할 수 있는 주제를 다뤄보고자 합니다. 그래서 선택한 주제는 AWS에서 제공하는 Aurora Serverless v2입니다. Aurora Serverless v2는 Aurora MySQL 호환 버전 및 PostgreSQL 호환 버전에 사용할 수 있습니다. 본 포스팅에서는 명칭의 혼선을 없애기 위해 Aurora Serverless(Aurora Serverless Mysql v2)와 Aurora MySQL(기존 Aurora MySQL)로 지칭하겠습니다. Aurora Serverless 에 대한 간략한 소개와 더불어 Aurora MySQL과 대조를 해 보고, 그 중 주목할만한 점에 대해 성능 위주로 공유하고자 합니다.참조 : Aurora Serverless v2 사용하기 — Amazon AuroraAmazon AuroraAurora Serverless의 개요 및 주요 특징Aurora Serverless란?Aurora Serverless는 Aurora MySQL에 대한 온디맨드 방식의 자동 크기 구성을 할 수 있게 해주는 AWS 서비스입니다. 기존에는 Aurora MySQL을 관리하려면 새로운 부하 상황을 미리 예상하여 증설해야만 했었습니다. 하지만, Aurora Serverless 는 애플리케이션 요구 사항에 따라 데이터베이스가 자동으로 확장/축소됩니다. 또한, 사용하지 않는 리소스에 대해서는 요금을 지불하지 않고 Aurora의 고가용성, 확장성을 그대로 활용할 수 있으며, 빠른 속도로 이용할 수 있습니다. 즉, 트래픽을 예측할 수 없을 때 적절한 구성으로 볼 수 있습니다Aurora Serverless는 v1, v2로 버전이 나뉘어져 있습니다. 그리고 Aurora MySQL은 v1,v2,v3가 있는데, 현재는 Aurora MySQL v3에서 Aurora Serverless v1 사용은 불가합니다.참조 : https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/Concepts.Aurora_Fea_Regions_DB-eng.Feature.ServerlessV1.html#Concepts.Aurora_Fea_Regions_DB-eng.Feature.ServerlessV1.amyAurora Serverless에선 ACU(Aurora Capacity Unit)라는 단위를 이용하여 Resource를 정하게 됩니다. 0.5 ~ 128 ACU까지 범위를 정할 수 있으며 최소단위 ACU로 초당 과금됩니다. 1 ACU= 대략 적으로 2GB Memory + CPU + Network 의 조합으로 이루어집니다. ACU는 ServerlessDatabaseCapacity와 ACUUtilization, 두 가지 지표를 통해 손쉽게 확인할 수 있습니다.참조 : Aurora Serverless v2의 성능 및 크기 조정 — Amazon AuroraScale Up / Down에 따라
awsauroradb
mysql
1/5/2025
Aurora Serverless 알아보기
안녕하세요, SRE팀에 DBA로 새로 합류한 브라우니입니다.AWS Service에 여러 서비스가 있지만 그 중에서도 여러 서비스에 활용할 수 있는 주제를 다뤄보고자 합니다. 그래서 선택한 주제는 AWS에서 제공하는 Aurora Serverless v2입니다. Aurora Serverless v2는 Aurora MySQL 호환 버전 및 PostgreSQL 호환 버전에 사용할 수 있습니다. 본 포스팅에서는 명칭의 혼선을 없애기 위해 Aurora Serverless(Aurora Serverless Mysql v2)와 Aurora MySQL(기존 Aurora MySQL)로 지칭하겠습니다. Aurora Serverless 에 대한 간략한 소개와 더불어 Aurora MySQL과 대조를 해 보고, 그 중 주목할만한 점에 대해 성능 위주로 공유하고자 합니다.참조 : Aurora Serverless v2 사용하기 — Amazon AuroraAmazon AuroraAurora Serverless의 개요 및 주요 특징Aurora Serverless란?Aurora Serverless는 Aurora MySQL에 대한 온디맨드 방식의 자동 크기 구성을 할 수 있게 해주는 AWS 서비스입니다. 기존에는 Aurora MySQL을 관리하려면 새로운 부하 상황을 미리 예상하여 증설해야만 했었습니다. 하지만, Aurora Serverless 는 애플리케이션 요구 사항에 따라 데이터베이스가 자동으로 확장/축소됩니다. 또한, 사용하지 않는 리소스에 대해서는 요금을 지불하지 않고 Aurora의 고가용성, 확장성을 그대로 활용할 수 있으며, 빠른 속도로 이용할 수 있습니다. 즉, 트래픽을 예측할 수 없을 때 적절한 구성으로 볼 수 있습니다Aurora Serverless는 v1, v2로 버전이 나뉘어져 있습니다. 그리고 Aurora MySQL은 v1,v2,v3가 있는데, 현재는 Aurora MySQL v3에서 Aurora Serverless v1 사용은 불가합니다.참조 : https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/Concepts.Aurora_Fea_Regions_DB-eng.Feature.ServerlessV1.html#Concepts.Aurora_Fea_Regions_DB-eng.Feature.ServerlessV1.amyAurora Serverless에선 ACU(Aurora Capacity Unit)라는 단위를 이용하여 Resource를 정하게 됩니다. 0.5 ~ 128 ACU까지 범위를 정할 수 있으며 최소단위 ACU로 초당 과금됩니다. 1 ACU= 대략 적으로 2GB Memory + CPU + Network 의 조합으로 이루어집니다. ACU는 ServerlessDatabaseCapacity와 ACUUtilization, 두 가지 지표를 통해 손쉽게 확인할 수 있습니다.참조 : Aurora Serverless v2의 성능 및 크기 조정 — Amazon AuroraScale Up / Down에 따라
2025.01.05
awsauroradb
mysql
좋아요
별로에요
Spring Boot 성능 개선 사례 공유 (1) - Redis 및 Local 캐싱 활용
이 번에는 Redis와 Local 캐시를 활용하여 조회 성능을 개선한 사례를 공유드려보려고 합니다.성능을 개선할 때 중요한 것은 왜, 어떤 목적으로 성능을 개선하는지 라고 생각하는데요.저의 경우에는 상점에서 팔고 있는 다양한 상품(코스튬, 꾸미기 아이템 등)들의 정보(이미지url, 가격, 할인정책 등)를 DB를 통해 조회하고 있었고(일부 Redis 캐시를 활용하고 있었으나 동일한 parameter로 조회했을 때만 사용) 조회가 몰릴 경우엔 Slow Query로 인한 부하가 발생하여 서버가 정상 동작하지 않는 이슈가 있었습니다.이를 개선하기 위해 조회할 때는 오직 로컬 메모리에 로드 된 캐시만을 활용하고 DB에서 데이터가 변경되었을 때Redis(글로벌캐시) 캐시 또는 Local 캐시(메모리에 load된 캐시)를 업데이트할 수 있는 구조에 대해 고민하였습니다.위에서도 언급했듯이 기존에도 일부 Redis 캐시를 사용하고 있었습니다.다만 parameter 단위로 쪼개서 캐싱을 하고 있어서 조회 조건이 변경될 경우 DB를 다시 조회한 뒤 Redis에 캐싱하고 사용할 수 있었습니다.이번에는 전체 데이터를 Redis에 올리고, 그 데이터를 로컬 메모리에 로드해서 조회가 일어날 때 로컬 메모리에 load된 cache 데이터로만 조회할 수 있도록 변경하였습니다.• None Admin에서 마켓 데이터 변경 시, Cache API 서버로 캐시 갱신 API 호출• None Cache API에서는 변경된 데이터를 DB에서 읽은 후, Redis에 저장된 캐시를 삭제하고 다시 올림• None Redis 캐시 갱신에 성공했다면, Redis에 event publish 요청 (이 때 변경된 데이터에 해당하는 topic에만 publish 요청)• None 해당 topic을 subscribe 하고 있는 API 서버는 publish한 event 요청을 받음• None event 요청을 받았다면 Redis에 캐싱된 데이터가 변경되었다는 의미이므로, Redis를 조회해서 데이터를 내려받은 후 local 메모리에 load• None 만약 Redis 장애가 발생하였을 경우, DB에서 데이터를 조회해서 local 메모리에 load• None 혹시라도 event pub/sub이 정상적으로 수행되지 않을 수 있으므로 배치 스케줄러로 N분(예: 60분)마다 캐시 갱신 API를 호출• None API로 조회 요청이 들어올 경우 로컬에 로드된 데이터로 조회해서 결과 응답 전송이를 구현하기 위해서 개발적으로 고민한 포인트 들에 대해서 같이 공유드리려고 합니다.• None Admin에서 데이터가 변경되었을 때 Redis에 캐싱된 데이터도 업데이트 해주어야 합니다. 그러나 보통 데이터는 아주 일부만 변경됩니다. 만약 Redis에 데이터를 올려둘 때 전체 리스트를 한 번에 올려둔다면 (예: key:value <- list전체) 변경한 데이터만 반영할 수 없습니다. 따라서 실제로 갱신한 몇 개의 데이터만 갱신할 수 있도록 Redis의 자료구조를 Hash 구조로 구성했습니다.• None DB에서의 PK
redis
1/3/2025
Spring Boot 성능 개선 사례 공유 (1) - Redis 및 Local 캐싱 활용
이 번에는 Redis와 Local 캐시를 활용하여 조회 성능을 개선한 사례를 공유드려보려고 합니다.성능을 개선할 때 중요한 것은 왜, 어떤 목적으로 성능을 개선하는지 라고 생각하는데요.저의 경우에는 상점에서 팔고 있는 다양한 상품(코스튬, 꾸미기 아이템 등)들의 정보(이미지url, 가격, 할인정책 등)를 DB를 통해 조회하고 있었고(일부 Redis 캐시를 활용하고 있었으나 동일한 parameter로 조회했을 때만 사용) 조회가 몰릴 경우엔 Slow Query로 인한 부하가 발생하여 서버가 정상 동작하지 않는 이슈가 있었습니다.이를 개선하기 위해 조회할 때는 오직 로컬 메모리에 로드 된 캐시만을 활용하고 DB에서 데이터가 변경되었을 때Redis(글로벌캐시) 캐시 또는 Local 캐시(메모리에 load된 캐시)를 업데이트할 수 있는 구조에 대해 고민하였습니다.위에서도 언급했듯이 기존에도 일부 Redis 캐시를 사용하고 있었습니다.다만 parameter 단위로 쪼개서 캐싱을 하고 있어서 조회 조건이 변경될 경우 DB를 다시 조회한 뒤 Redis에 캐싱하고 사용할 수 있었습니다.이번에는 전체 데이터를 Redis에 올리고, 그 데이터를 로컬 메모리에 로드해서 조회가 일어날 때 로컬 메모리에 load된 cache 데이터로만 조회할 수 있도록 변경하였습니다.• None Admin에서 마켓 데이터 변경 시, Cache API 서버로 캐시 갱신 API 호출• None Cache API에서는 변경된 데이터를 DB에서 읽은 후, Redis에 저장된 캐시를 삭제하고 다시 올림• None Redis 캐시 갱신에 성공했다면, Redis에 event publish 요청 (이 때 변경된 데이터에 해당하는 topic에만 publish 요청)• None 해당 topic을 subscribe 하고 있는 API 서버는 publish한 event 요청을 받음• None event 요청을 받았다면 Redis에 캐싱된 데이터가 변경되었다는 의미이므로, Redis를 조회해서 데이터를 내려받은 후 local 메모리에 load• None 만약 Redis 장애가 발생하였을 경우, DB에서 데이터를 조회해서 local 메모리에 load• None 혹시라도 event pub/sub이 정상적으로 수행되지 않을 수 있으므로 배치 스케줄러로 N분(예: 60분)마다 캐시 갱신 API를 호출• None API로 조회 요청이 들어올 경우 로컬에 로드된 데이터로 조회해서 결과 응답 전송이를 구현하기 위해서 개발적으로 고민한 포인트 들에 대해서 같이 공유드리려고 합니다.• None Admin에서 데이터가 변경되었을 때 Redis에 캐싱된 데이터도 업데이트 해주어야 합니다. 그러나 보통 데이터는 아주 일부만 변경됩니다. 만약 Redis에 데이터를 올려둘 때 전체 리스트를 한 번에 올려둔다면 (예: key:value <- list전체) 변경한 데이터만 반영할 수 없습니다. 따라서 실제로 갱신한 몇 개의 데이터만 갱신할 수 있도록 Redis의 자료구조를 Hash 구조로 구성했습니다.• None DB에서의 PK
2025.01.03
redis
좋아요
별로에요
[#4 LLM Tutorial With RAG] LLM의무한한 가능성을 해방하는 LangChain
안녕하세요, 현대자동차 기초소재연구센터에서 근무하는 박상인 연구원 입니다.Tutorial #3에서 LangChain Library를 활용해서 가장 낮은 수준의 RAG 기술을 간단히 구현했습니다. 기존에는 1)질문 2)LLM 3)답변의 순서로 LLM을 사용했다면, RAG를 통해서 1)질문 2)관련 정보 검색 3) 질문 + 관련 정보 4) LLM 5)답변의 순서로 LLM을 사용할 수 있게 되었습니다. 화살표( )의 흐름들은 답변 생성의 파이프 라인(데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태)입니다.LangChain에서는 RAG의 여러 구성 요소(질문, 관련 정보 검색기, 벡터 스토어 등)들을 결합하는 Chain이라는 Python Class를 사용해서 파이프 라인을 정의합니다. 이번 Tutorial에서는 답변 생성 파이프 라인인 Chain을 정의하는 LangChain Expression Language(LCEL) Syntax와 웹 크롤링(Web Crawling)을 통한 벡터 스토어 생성을 다룹니다. RAG Tutorial #4 내용 시작합니다출처 : https://levelup.gitconnected.com/implementing-rag-using-langchain-and-singlestore-a-step-by-step-guide-2a579da1de0c이번 Tutorial은 위의 RAG Workflow 그림을 그대로 구현하는 것을 목표로 합니다. LCEL 기초 : 1)질문 2)LLM 3)답변의 생성 파이프 라인을 갖는 Chain 생성 웹 크롤링을 활용한 벡터 스토어 생성 : RAG 구현의 핵심 중 하나인 Cheating Sheet 생성 LCEL 응용 : 1)질문 2)관련 정보 검색 3) 질문 + 관련 정보 4) LLM 5)답변의 생성 파이프 라인을 갖는 Chain 생성# Import 될 라이브러리는 아래와 같습니다from langchain_community.chat_models import ChatOllamafrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnablePassthroughfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import PromptTemplatefrom langchain_community.vectorstores import FAISSfrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_community.embeddings import OllamaEmbeddingsfrom langchain.text_splitter import RecursiveCharacterTextSplitterimport bs4import sslimport urllib3Implementation : 버티컬 라인(|)으로 구현하는 Chain# chain_level_1 : 입력 LLM 출력# 입력 정의(ver1)input_prompt = PromptTemplate(template="너는 {question}에 대해 한국어로 친절하고 간결하게 대답하는 인공지능이야", input_variables=["question"])# 입력 정의(ver2)template = "너는 {question}에 대해 한국어로 친절하고 간결하게 대답하는 인공지능이야"input_prompt = ChatPromptTemplate.from_template(template)# LLM 정의 : Ollama에서 설치했던 모델 입력llm = ChatOllama(model='my_llm:latest',temperature=0)# 출력 정의 : LLM의 출력을 문자열 형식으로 변환output_parser = StrOutputParser()# Chain 정의 : 버티컬 라인(|)을 통해서 Chain의 파이프 라인 정의chain_level_1 = {'question': RunnablePassthrough()} | input_prompt | llm | output_parser# 출력 생성print(chain_level_1.invoke({"question':'배고픈데 점심 추천해줘"}))Implementation 에서 구현될 Chain은 prompt, llm, parser라는 3개의 요소를 갖습니다. 3개의 요소들을 구동 순서에 맞게 버티컬 라인(|)으로 이어서 chain_level_1을 정의했습니다.Tutorial #3에서 Prompt는 LLM이 어떻게 동작할지 사용자가 제어할 수 있는 일종의 틀(Template) 이라고 소개 드렸습니다. input_prompt 변수를 정의하는 코드를 살펴보면, question이라는 key 의 해당하는 value 를 입력으로 받는 구조를 갖고 있습니다. 예를 들어 input_prompt.invoke({ question : 현대자동차의 직원은 총 몇 명이야? }라는 코드를 실행하면, "너는 현대자동차의 직원은 총 몇 명이야? 에 대해 한국어로 친절하고 간결하게 대답하는 인공지능이야"라는 제어 명령을 LLM에게 입력 값으로 전달합니다.llm 변수는 Tutorial #2에서 ollama를 통해 설치했던 LLM 모델의 이름을 작성하면 됩니다. 제가 소개드린 모델 말고도 성능이 검증된 신규 모델이 나오면 교체하면 더 좋을 것 같습니다. 최근에 LG AI RESEARCH에서 나온 Exaone 3.5(https://huggingface.co/LGAI-EXAONE/EXAONE-3.5-2.4B-Instruct)을 사용해 봤는데 성능이 굉장히 좋아서 추가로 추천드립니다.llm.invoke(input_prompt)라고 코드를 실행하면 langchain_core.messages.ai.AIMessage 클래스를 반환 합니다. 이 안에는 여러 값이 있지만, 실제로 사용자에게 필요한 내용은 LLM이 생성한 답변에 있는 '문자'입니다. output_parser는 LLM이 생성한 답변을 사용자에게 필요한 형태로
1/2/2025
[#4 LLM Tutorial With RAG] LLM의무한한 가능성을 해방하는 LangChain
안녕하세요, 현대자동차 기초소재연구센터에서 근무하는 박상인 연구원 입니다.Tutorial #3에서 LangChain Library를 활용해서 가장 낮은 수준의 RAG 기술을 간단히 구현했습니다. 기존에는 1)질문 2)LLM 3)답변의 순서로 LLM을 사용했다면, RAG를 통해서 1)질문 2)관련 정보 검색 3) 질문 + 관련 정보 4) LLM 5)답변의 순서로 LLM을 사용할 수 있게 되었습니다. 화살표( )의 흐름들은 답변 생성의 파이프 라인(데이터 처리 단계의 출력이 다음 단계의 입력으로 이어지는 형태)입니다.LangChain에서는 RAG의 여러 구성 요소(질문, 관련 정보 검색기, 벡터 스토어 등)들을 결합하는 Chain이라는 Python Class를 사용해서 파이프 라인을 정의합니다. 이번 Tutorial에서는 답변 생성 파이프 라인인 Chain을 정의하는 LangChain Expression Language(LCEL) Syntax와 웹 크롤링(Web Crawling)을 통한 벡터 스토어 생성을 다룹니다. RAG Tutorial #4 내용 시작합니다출처 : https://levelup.gitconnected.com/implementing-rag-using-langchain-and-singlestore-a-step-by-step-guide-2a579da1de0c이번 Tutorial은 위의 RAG Workflow 그림을 그대로 구현하는 것을 목표로 합니다. LCEL 기초 : 1)질문 2)LLM 3)답변의 생성 파이프 라인을 갖는 Chain 생성 웹 크롤링을 활용한 벡터 스토어 생성 : RAG 구현의 핵심 중 하나인 Cheating Sheet 생성 LCEL 응용 : 1)질문 2)관련 정보 검색 3) 질문 + 관련 정보 4) LLM 5)답변의 생성 파이프 라인을 갖는 Chain 생성# Import 될 라이브러리는 아래와 같습니다from langchain_community.chat_models import ChatOllamafrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnablePassthroughfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import PromptTemplatefrom langchain_community.vectorstores import FAISSfrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_community.embeddings import OllamaEmbeddingsfrom langchain.text_splitter import RecursiveCharacterTextSplitterimport bs4import sslimport urllib3Implementation : 버티컬 라인(|)으로 구현하는 Chain# chain_level_1 : 입력 LLM 출력# 입력 정의(ver1)input_prompt = PromptTemplate(template="너는 {question}에 대해 한국어로 친절하고 간결하게 대답하는 인공지능이야", input_variables=["question"])# 입력 정의(ver2)template = "너는 {question}에 대해 한국어로 친절하고 간결하게 대답하는 인공지능이야"input_prompt = ChatPromptTemplate.from_template(template)# LLM 정의 : Ollama에서 설치했던 모델 입력llm = ChatOllama(model='my_llm:latest',temperature=0)# 출력 정의 : LLM의 출력을 문자열 형식으로 변환output_parser = StrOutputParser()# Chain 정의 : 버티컬 라인(|)을 통해서 Chain의 파이프 라인 정의chain_level_1 = {'question': RunnablePassthrough()} | input_prompt | llm | output_parser# 출력 생성print(chain_level_1.invoke({"question':'배고픈데 점심 추천해줘"}))Implementation 에서 구현될 Chain은 prompt, llm, parser라는 3개의 요소를 갖습니다. 3개의 요소들을 구동 순서에 맞게 버티컬 라인(|)으로 이어서 chain_level_1을 정의했습니다.Tutorial #3에서 Prompt는 LLM이 어떻게 동작할지 사용자가 제어할 수 있는 일종의 틀(Template) 이라고 소개 드렸습니다. input_prompt 변수를 정의하는 코드를 살펴보면, question이라는 key 의 해당하는 value 를 입력으로 받는 구조를 갖고 있습니다. 예를 들어 input_prompt.invoke({ question : 현대자동차의 직원은 총 몇 명이야? }라는 코드를 실행하면, "너는 현대자동차의 직원은 총 몇 명이야? 에 대해 한국어로 친절하고 간결하게 대답하는 인공지능이야"라는 제어 명령을 LLM에게 입력 값으로 전달합니다.llm 변수는 Tutorial #2에서 ollama를 통해 설치했던 LLM 모델의 이름을 작성하면 됩니다. 제가 소개드린 모델 말고도 성능이 검증된 신규 모델이 나오면 교체하면 더 좋을 것 같습니다. 최근에 LG AI RESEARCH에서 나온 Exaone 3.5(https://huggingface.co/LGAI-EXAONE/EXAONE-3.5-2.4B-Instruct)을 사용해 봤는데 성능이 굉장히 좋아서 추가로 추천드립니다.llm.invoke(input_prompt)라고 코드를 실행하면 langchain_core.messages.ai.AIMessage 클래스를 반환 합니다. 이 안에는 여러 값이 있지만, 실제로 사용자에게 필요한 내용은 LLM이 생성한 답변에 있는 '문자'입니다. output_parser는 LLM이 생성한 답변을 사용자에게 필요한 형태로
2025.01.02
좋아요
별로에요
시각장애인을 위해 모달 컴포넌트 접근성 개선하기
일반인들은 마우스로 원하는 링크나 버튼을 눌러 웹 페이지를 자유롭게 탐색할 수 있지만,시각장애인들은 주로 키보드의 Tab 또는 Shift + Tab 또는 별도의 장치를 통해 HTML 태그를 계층적으로 탐색합니다.따라서 프론트엔드 개발자들은 시각장애인들이 일반인과 비슷한 수준의 탐색을 경험할 수 있도록 접근성을 신경쓸 필요가 있는데요,오늘은 모달(팝업) 컴포넌트를 개발할 때 놓치기 쉬운 접근성을 다뤄 보려 합니다.예시를 위해 간단한 모달을 제작한 모습입니다.일반인은 짙은 회색의 Backdrop 영역과 흰 배경을 통해 모달이 열린 것을 알 수 있기 때문에 평범한 모달처럼 보이지만,시각장애인들은 스크린 리더가 읽어주는 HTML 태그의 속성만으로 이를 구분해야 하기 때문에 팝업이 열리기는 했는지,이것이 팝업이 맞기는 한지 인식하기 어려워할 가능성이 있습니다.가장 기본적인 방법은 스크린 리더가 UI의 힌트를 제공할 수 있도록 접근성 속성을 추가하는 것입니다.모달 컴포넌트에 , 속성을 부여하면 스크린 리더는 이것이 평범한 나 태그가 아닌 모달 UI라는 힌트를 시각장애인에게 전달할 수 있게 됩니다.크롬 개발자 도구에서 접근성 트리를 확인해 보면 마크업 구조는 수정하지 않았음에도 "닫기" 버튼이 본문과 함께 모달 내에 묶이게 되었고, 현재 탐색하고 있는 UI가 "오늘의 주요 뉴스" 모달임을 명시적으로 안내할 수 있게 되었습니다.개선점 2. Escape 키로 모달을 닫을 수 있도록 추가하기앞서 말했듯 시각장애인은 "닫기" 버튼을 곧바로 마우스로 클릭할 수 없고, 키보드 또는 보조 입력 도구를 통해 HTML 계층을 순차적으로 탐색하게 됩니다.따라서 컨텐츠가 많은 팝업에서는 컨텐츠를 조회하다가 팝업을 닫기 위해 뒤로가기 키를 여러 차례 눌러야 하는 불편을 겪을 수 있는데요,이런 경험을 개선하기 위해 ESC 키를 눌렀을 때 팝업이 닫히는 기능을 추가할 필요가 있습니다.이처럼 이벤트에 간단한 핸들러를 추가하는 것으로 시각장애인들이 컨텐츠를 읽다가 Shfit + Tab을 수 차례 눌러 닫기 버튼을 다시 찾아야 하는 불편을 해결할 수 있게 되었습니다.이로써 여러 불편점이 개선되었지만 아직 한 가지 문제가 남아 있습니다.시각장애인이 Tab 키를 통해 페이지를 탐색하는 과정에서 모달 바깥의 요소가 focus 동작의 대상이 될 수 있다는 점인데요,이로 인해 UI상 기존 화면 위에 오버레이된 모달과 그 밑에 깔린 요소들을 분간할 수 없게 되어 탐색에 혼란을 주게 된다는 문제가 있습니다.사진을 보면 느끼시겠지만 탭 키를 통한 탐색이 모달을 넘어 헤더 영역까지 침범하고 있는데요,이를 해결할 수 있는 방법이 바로 focus trap 이라는 기법입니다.말 그대로 포커스를 가둔다는 의미로 모달이 열려 있는 동안에는 모달 내부의 요소들만 탭을 통해 포커스가 가능하도록 하는 것인데요,요즘은 이를 직접 구현하지 않아도 focus-trap (바닐라) 나 focus-trap-vue, focus-trap-react 를 통해 간단하게 구현할 수 있습니다.이제 시각장애인이 Tab 키를 실수로
1/2/2025
시각장애인을 위해 모달 컴포넌트 접근성 개선하기
일반인들은 마우스로 원하는 링크나 버튼을 눌러 웹 페이지를 자유롭게 탐색할 수 있지만,시각장애인들은 주로 키보드의 Tab 또는 Shift + Tab 또는 별도의 장치를 통해 HTML 태그를 계층적으로 탐색합니다.따라서 프론트엔드 개발자들은 시각장애인들이 일반인과 비슷한 수준의 탐색을 경험할 수 있도록 접근성을 신경쓸 필요가 있는데요,오늘은 모달(팝업) 컴포넌트를 개발할 때 놓치기 쉬운 접근성을 다뤄 보려 합니다.예시를 위해 간단한 모달을 제작한 모습입니다.일반인은 짙은 회색의 Backdrop 영역과 흰 배경을 통해 모달이 열린 것을 알 수 있기 때문에 평범한 모달처럼 보이지만,시각장애인들은 스크린 리더가 읽어주는 HTML 태그의 속성만으로 이를 구분해야 하기 때문에 팝업이 열리기는 했는지,이것이 팝업이 맞기는 한지 인식하기 어려워할 가능성이 있습니다.가장 기본적인 방법은 스크린 리더가 UI의 힌트를 제공할 수 있도록 접근성 속성을 추가하는 것입니다.모달 컴포넌트에 , 속성을 부여하면 스크린 리더는 이것이 평범한 나 태그가 아닌 모달 UI라는 힌트를 시각장애인에게 전달할 수 있게 됩니다.크롬 개발자 도구에서 접근성 트리를 확인해 보면 마크업 구조는 수정하지 않았음에도 "닫기" 버튼이 본문과 함께 모달 내에 묶이게 되었고, 현재 탐색하고 있는 UI가 "오늘의 주요 뉴스" 모달임을 명시적으로 안내할 수 있게 되었습니다.개선점 2. Escape 키로 모달을 닫을 수 있도록 추가하기앞서 말했듯 시각장애인은 "닫기" 버튼을 곧바로 마우스로 클릭할 수 없고, 키보드 또는 보조 입력 도구를 통해 HTML 계층을 순차적으로 탐색하게 됩니다.따라서 컨텐츠가 많은 팝업에서는 컨텐츠를 조회하다가 팝업을 닫기 위해 뒤로가기 키를 여러 차례 눌러야 하는 불편을 겪을 수 있는데요,이런 경험을 개선하기 위해 ESC 키를 눌렀을 때 팝업이 닫히는 기능을 추가할 필요가 있습니다.이처럼 이벤트에 간단한 핸들러를 추가하는 것으로 시각장애인들이 컨텐츠를 읽다가 Shfit + Tab을 수 차례 눌러 닫기 버튼을 다시 찾아야 하는 불편을 해결할 수 있게 되었습니다.이로써 여러 불편점이 개선되었지만 아직 한 가지 문제가 남아 있습니다.시각장애인이 Tab 키를 통해 페이지를 탐색하는 과정에서 모달 바깥의 요소가 focus 동작의 대상이 될 수 있다는 점인데요,이로 인해 UI상 기존 화면 위에 오버레이된 모달과 그 밑에 깔린 요소들을 분간할 수 없게 되어 탐색에 혼란을 주게 된다는 문제가 있습니다.사진을 보면 느끼시겠지만 탭 키를 통한 탐색이 모달을 넘어 헤더 영역까지 침범하고 있는데요,이를 해결할 수 있는 방법이 바로 focus trap 이라는 기법입니다.말 그대로 포커스를 가둔다는 의미로 모달이 열려 있는 동안에는 모달 내부의 요소들만 탭을 통해 포커스가 가능하도록 하는 것인데요,요즘은 이를 직접 구현하지 않아도 focus-trap (바닐라) 나 focus-trap-vue, focus-trap-react 를 통해 간단하게 구현할 수 있습니다.이제 시각장애인이 Tab 키를 실수로
2025.01.02
좋아요
별로에요