<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>finda-tech.log</title>
        <link>https://velog.io/</link>
        <description>대덕소프트웨어마이스터고 봉사 활동 관리 서비스 Finda입니다</description>
        <lastBuildDate>Thu, 26 Mar 2026 13:56:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>finda-tech.log</title>
            <url>https://velog.velcdn.com/images/finda-tech/profile/88e7c183-1716-47c4-8794-fd8be802ab9e/image.svg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. finda-tech.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/finda-tech" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[BE] 학교 구성원 250명 교내 서비스에서 MSA 사용하기]]></title>
            <link>https://velog.io/@finda-tech/BE-%ED%95%99%EA%B5%90-%EA%B5%AC%EC%84%B1%EC%9B%90-250%EB%AA%85-%EA%B5%90%EB%82%B4-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%97%90%EC%84%9C-MSA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@finda-tech/BE-%ED%95%99%EA%B5%90-%EA%B5%AC%EC%84%B1%EC%9B%90-250%EB%AA%85-%EA%B5%90%EB%82%B4-%EC%84%9C%EB%B9%84%EC%8A%A4%EC%97%90%EC%84%9C-MSA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 26 Mar 2026 13:56:13 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하기에-앞서">시작하기에 앞서</h3>
<p>안녕하세요.
대덕소프트웨어마이스터고등학교 2학년 <strong>Team Finda Backend</strong>의 변도휘, 박지민입니다.</p>
<p>이번 글에서는 <strong>학습을 목적으로 프로젝트에 MSA를 도입하며 겪은 고민과 설계 과정</strong>, 그리고 적용 후기를 정리했습니다.</p>
<p>아직 학습 단계이기 때문에 부족한 점이나 개선할 부분이 있다면 편하게 피드백 주시면 감사하겠습니다.</p>
<hr>
<h3 id="학교-프로젝트에서-msa를-써도-될까">학교 프로젝트에서 MSA를 써도 될까?</h3>
<p>MSA를 도입하기 전, 여러 학우분들께 의견을 구했습니다.</p>
<p>하지만 돌아온 답변은 대부분 비슷했습니다.</p>
<blockquote>
<p>&quot;학교 프로젝트에 MSA는 <strong>오버엔지니어링</strong> 아닌가요?&quot;</p>
<p>&quot;트래픽도 크지 않은데 굳이 MSA를 쓸 이유가 있나요?&quot;</p>
<p>&quot;모놀리식이 더 합리적인 선택일 것 같습니다.&quot;</p>
</blockquote>
<p>실제로 맞는 말이었습니다.</p>
<p>교내 프로젝트 특성상 큰 트래픽이 발생하지 않고, 서비스 규모만 놓고 보면 <strong>모놀리식 아키텍처로도 충분히 구현이 가능</strong>했습니다.</p>
<p>오히려 MSA를 도입하면 서비스 분리, 통신, 배포, 인증 등 고려해야 할 요소가 늘어나기 때문에 <strong>운영 복잡도만 증가</strong>할 가능성이 높았습니다.</p>
<p>그럼에도 저희는 MSA를 선택했습니다.</p>
<p>이 프로젝트의 목표는 <strong>기능 구현이 아니라 아키텍처 학습</strong>이었기 때문입니다.
오버엔지니어링이라는 것을 알면서도 <strong>직접 경험해보는 것이 더 중요</strong>하다고 판단했습니다.</p>
<hr>
<h3 id="msa-전에-ddd부터-진행했습니다">MSA 전에 DDD부터 진행했습니다</h3>
<p>MSA를 도입하기로 결정했지만 바로 서비스를 분리하지는 않았습니다.</p>
<p>단순히 서비스를 나누는 것보다 <strong>도메인 경계를 먼저 정의하는 것이 더 중요</strong>하다고 판단했기 때문입니다.</p>
<p>서비스 기준으로 먼저 분리하게 되면 <strong>책임이 모호</strong>해지고, 잘못된 경계로 인해 <strong>서비스 간 결합도</strong>가 오히려 높아질 수 있습니다.</p>
<p>따라서 <strong>DDD 기반으로 도메인을 먼저 정의</strong>한 뒤, 그 경계를 기준으로 서비스를 분리하기로 했습니다.</p>
<p>이를 위해 Finda 백엔드 팀은 DDD 개념을 학습하며 <strong>유비쿼터스 언어</strong>와 <strong>바운디드 컨텍스트</strong>를 정의하는 과정을 진행했습니다.</p>
<hr>
<h3 id="유비쿼터스-언어-정의">유비쿼터스 언어 정의</h3>
<p>먼저 서비스에서 사용되는 핵심 개념들을 정리했습니다.</p>
<ul>
<li>봉사활동 (Volunteer)</li>
<li>봉사 신청 (Application)</li>
<li>참여 인원 (Participant)</li>
<li>공지사항 (Notice)</li>
<li>알림 (Notification)</li>
<li>사용자 (User)</li>
<li>권한 (Role)</li>
</ul>
<p>이 과정에서 팀 내부에서 같은 의미를 서로 다른 용어로 사용할 가능성이 있다고 판단했습니다.</p>
<p>예를 들어 &quot;봉사 신청&quot;, &quot;참여&quot;, &quot;지원&quot; 같은 용어가 혼용될 수 있고, &quot;공지&quot;, &quot;공지사항&quot;, &quot;안내&quot; 역시 동일한 의미로 사용될 수 있습니다.</p>
<p>이처럼 <strong>용어가 명확히 정의되지 않으면</strong> 도메인 모델과 API 설계 과정에서 혼란이 발생할 수 있습니다.</p>
<p>따라서 팀 내에서 <strong>사용할 용어를 하나로 통일</strong>하고, 해당 유비쿼터스 언어를 기준으로 도메인을 정의했습니다.</p>
<p>이 과정을 통해 설계 단계부터 <strong>용어 혼란을 줄이고</strong>, 도메인 모델의 <strong>일관성을 유지</strong>할 수 있었습니다.</p>
<hr>
<h3 id="바운디드-컨텍스트-분리">바운디드 컨텍스트 분리</h3>
<p>유비쿼터스 언어를 정리한 이후 도메인 간 책임을 기준으로 경계를 분리했습니다.</p>
<p>그 결과 다음과 같은 <strong>세 개의 바운디드 컨텍스트</strong>를 도출했습니다.</p>
<p><img src="https://velog.velcdn.com/images/finda-tech/post/764ff42a-9e69-4a48-82c3-afb79aaecf37/image.png" alt=""></p>
<ul>
<li>Volunteer</li>
<li>Notification</li>
<li>Auth</li>
</ul>
<p>각 도메인은 다음과 같은 역할을 담당합니다.</p>
<h4 id="volunteer">Volunteer</h4>
<p>봉사활동 생성, 수정, 삭제 및 신청과 관련된 <strong>핵심 비즈니스 로직</strong>을 담당합니다.</p>
<p>Finda 서비스의 <strong>중심이 되는 도메인</strong>입니다.</p>
<h4 id="notification">Notification</h4>
<p>공지사항 및 봉사활동과 관련된 알림 기능을 담당합니다.</p>
<p>이벤트 기반으로 사용자에게 알림을 전달하는 역할을 수행합니다.</p>
<p>Notification을 별도 서비스로 분리한 주된 이유는 <strong>장애 격리</strong>입니다. 알림 발송이 실패하더라도 <strong>봉사 신청 흐름 자체에는 영향</strong>을 주지 않아야 하기 때문입니다. 알림이라는 부가 기능 때문에 핵심 비즈니스 로직이 중단되는 상황을 방지하기 위해 별도 컨텍스트로 분리했습니다.</p>
<h4 id="auth">Auth</h4>
<p>사용자 인증 및 인가를 담당하는 도메인입니다.</p>
<p>로그인, 토큰 발급, 권한 관리 등의 인증 관련 기능을 처리합니다.</p>
<p>또한 이벤트 기반 처리를 위해 별도의 <strong>Batch 모듈</strong>을 구성했습니다. Batch 모듈은 도메인 이벤트를 <strong>비동기로 처리</strong>하는 모듈로, 특정 바운디드 컨텍스트에 속하기보다는 서비스 전반을 지원하는 역할을 담당합니다. 구체적인 구성과 이벤트 처리 흐름은 이후 섹션에서 다루도록 하겠습니다.</p>
<hr>
<h3 id="ddd-→-msa-설계-흐름">DDD → MSA 설계 흐름</h3>
<p>결과적으로 설계 흐름은 다음과 같이 진행되었습니다.</p>
<pre><code>도메인 분석

↓

유비쿼터스 언어 정의

↓

바운디드 컨텍스트 분리

↓

서비스 경계 정의

↓

MSA 서비스 분리</code></pre><p>이 과정을 통해 단순히 기술 기준이 아닌 <strong>도메인 기준으로 서비스를 분리</strong>할 수 있었습니다.</p>
<p>또한 <strong>서비스 간 책임이 명확</strong>해지면서 각 서비스가 독립적으로 변경될 수 있는 구조를 설계할 수 있었습니다.</p>
<hr>
<h3 id="전체-서버-흐름">전체 서버 흐름</h3>
<p>도메인을 분리한 뒤, 이를 기반으로 전체 서버 구조를 구성했습니다.</p>
<p><img src="https://velog.velcdn.com/images/finda-tech/post/2f69c70c-489c-4dbe-a3ce-e7767ca3c893/image.png" alt=""></p>
<p>클라이언트 요청은 <strong>API Gateway를 통해 단일 진입점</strong>으로 들어오며, Gateway에서 인증을 처리한 뒤 각 도메인 서비스로 전달됩니다.</p>
<p>각 서비스는 <strong>독립적인 데이터베이스</strong>를 가지며, 서비스 간 통신은 내부 네트워크를 통해 이루어집니다.</p>
<p>이벤트 기반 처리를 위해 별도의 Batch 서버를 구성했으며, 자세한 내용은 다음 섹션에서 다룹니다.</p>
<hr>
<h3 id="api-gateway-도입">API Gateway 도입</h3>
<p>서비스가 분리되면 클라이언트는 <strong>여러 서버를 직접 호출해야 하는 문제</strong>가 발생합니다.</p>
<p>이를 해결하기 위해 <strong>API Gateway를 단일 진입점</strong>으로 구성했습니다.</p>
<p>모든 외부 요청은 Gateway를 통해 들어오고, <strong>Gateway에서 인증 처리 후 각 서비스로 라우팅</strong>됩니다.</p>
<p>이를 통해 각 서비스는 인증 로직 없이 <strong>비즈니스 로직에만 집중</strong>할 수 있도록 구성했습니다.</p>
<hr>
<h3 id="외부-토큰--내부-토큰-구조">외부 토큰 / 내부 토큰 구조</h3>
<p>Gateway를 도입하면서 한 가지 고민이 생겼습니다.</p>
<p><strong>&quot;외부에서 전달된 사용자 토큰을 내부 서비스에도 그대로 전달해도 될까?&quot;</strong></p>
<p>이 방식은 <strong>서비스 간 결합도</strong>를 높이고, 보안 측면에서도 적절하지 않다고 판단했습니다.</p>
<p>그래서 <strong>외부 토큰과 내부 토큰을 분리하는 구조</strong>를 선택했습니다.</p>
<ul>
<li><strong>외부 토큰 :</strong> 사용자 인증 JWT</li>
<li><strong>내부 토큰 :</strong> 서비스 간 통신용 토큰</li>
</ul>
<p>Gateway에서 외부 토큰을 검증한 뒤, 내부 서비스 호출 시 <strong>내부 토큰을 새로 생성하여 전달</strong>하도록 구성했습니다.</p>
<p>이를 통해 <strong>내부 서비스는 Gateway만 신뢰</strong>하고 요청을 처리할 수 있도록 설계했습니다.</p>
<p>내부 토큰은 <strong>요청 단위로 짧은 만료 시간</strong>을 가지도록 설정했습니다. 다만 장기 실행 요청이나 비동기 처리가 필요한 경우의 토큰 갱신 전략은 현재도 검토 중인 부분으로, 이후 개선 과정에서 다룰 예정입니다.</p>
<hr>
<h3 id="rest-대신-grpc-선택">REST 대신 gRPC 선택</h3>
<p>서비스 간 통신 방식을 결정하는 데도 고민이 필요했습니다.
<strong>REST API와 gRPC</strong> 두 가지를 두고 검토했는데
MSA 환경에서는 <strong>서비스 간 호출이 빈번하게 발생</strong>하기 때문에
통신 방식의 선택이 <strong>전체 성능에 영향</strong>을 줄 수 있다고 판단했습니다.
REST는 JSON 기반 통신이라 호출이 많아질수록 <strong>직렬화 오버헤드가 커질 수 있어</strong>
내부 서비스 간 통신은 <strong>gRPC를 선택</strong>했습니다.</p>
<ul>
<li>HTTP/2 기반 통신</li>
<li>바이너리 프로토콜로 빠른 직렬화</li>
<li>Proto 파일 기반의 명확한 인터페이스 정의</li>
<li>브라우저 호환성을 고려할 필요 없는 내부 통신 환경</li>
</ul>
<p>이러한 이유로 서비스 간 통신은 <strong>gRPC 기반</strong>으로 구성했습니다.</p>
<hr>
<h3 id="마무리">마무리</h3>
<p>이번 프로젝트에서 MSA는 분명 <strong>오버엔지니어링</strong>이었습니다.</p>
<p>하지만 직접 설계하고 적용해보는 과정에서, 단순히 서비스를 나누는 것이 아니라</p>
<ul>
<li><strong>도메인 경계를 정의</strong>하고</li>
<li><strong>서비스 간 통신을 설계</strong>하고</li>
<li><strong>인증 구조를 분리</strong>하고</li>
<li><strong>배포 단위를 고민</strong>하는</li>
</ul>
<p>MSA의 전체적인 설계 흐름을 직접 경험할 수 있었습니다.</p>
<p>결과적으로 이번 프로젝트는 기능 구현보다 <strong>아키텍처를 고민하는 경험</strong>을 얻을 수 있었던 시간이었습니다.</p>
<p>아직 배우는 단계라 부족한 점이 많지만 끝까지 읽어주셔서 감사합니다.</p>
<p>피드백은 언제나 환영합니다! 🙇‍♀️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[iOS] SwiftUI 프로젝트에 TCA 도입하기- 개념 정리]]></title>
            <link>https://velog.io/@finda-tech/iOS-SwiftUI-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-TCA-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@finda-tech/iOS-SwiftUI-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-TCA-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 15 Jan 2026 06:53:58 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하기에-앞서">시작하기에 앞서</h3>
<p>안녕하세요.
대덕소프트웨어마이스터고등학교 2학년 <strong>Team Finda iOS</strong>의 하원입니다.</p>
<p>본 글은 프로젝트를 설계하기 위해 필요한 지식을 공부하여 정리한 글입니다.
부족한 점이나 틀린 부분이 있다면 편하게 말씀해 주시면 감사하겠습니다.</p>
<p>또한, 가독성을 위해  ~다 체를 사용하는 점 미리 양해 부탁드립니다. 🙇</p>
<hr>
<p>기존에 UIKit 기반에서 MVVM + Clean Architecture를 많이 사용해왔다.
그러나 최근 iOS 개발 트렌드를 살펴보면, 
반응형 UI를 중심으로 SwiftUI 사용이 점차 확대되고 있음을 알 수 있다.</p>
<p>이러한 변화 속에서 기존 개발 방식에도 변화가 필요하다고 느꼈고,
SwiftUI와 궁합이 좋다고 알려진 TCA(The Composable Architecture)를 프로젝트에 적용해보고자 한다.</p>
<p>Github 참고
<a href="https://github.com/pointfreeco/swift-composable-architecture">https://github.com/pointfreeco/swift-composable-architecture</a>
블로그 형식 및 내용 참고
<a href="%5Bhttps://medium.com/@youable.framios/whats-your-eta-what-s-your-tca-mmm-hmm-swiftui-tca%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%A0%84%EC%97%90-%EB%B3%B4%EB%A9%B4-%EC%A2%8B%EC%9D%80-%EA%B8%80-6cfeb92dbc29%5D(https://medium.com/@youable.framios/whats-your-eta-what-s-your-tca-mmm-hmm-swiftui-tca%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-%EC%A0%84%EC%97%90-%EB%B3%B4%EB%A9%B4-%EC%A2%8B%EC%9D%80-%EA%B8%80-6cfeb92dbc29)">What’s your ETA What’s your TCA ~Mmm-hmm (SwiftUI TCA를 시작하기 전에 보면 좋은 글)</a></p>
<hr>
<h3 id="tca란">TCA란?</h3>
<p>TCA(The Composable Architecture)은 구성, 테스트 및 사용 편의성을 고려하여 일관되고 이해하기 쉬운 방식으로 애플리케이션을 구축하기 위한 라이브러리다.</p>
<p>SwiftUI, UIKit 등을 비롯한 다양한 프레임워크에서 사용할 수 있으나, SwiftUI랑 찰떡궁합으로 알고 있다. 👍</p>
<p>저 위에 있는 Github 링크에 따르면,
다양한 목적과 복잡성을 가진 애플리케이션을 구축하는 데 사용할 수 있는 몇 가지 핵심 도구를 제공한다고 한다.</p>
<p>또한 <strong>State management</strong>, <strong>Composition</strong>, <strong>Side effects</strong>, <strong>Testing, Ergonomics</strong>의 문제를 해결한다고 한다.</p>
<h3 id="tca-어떻게-사용할까">TCA 어떻게 사용할까?</h3>
<p>TCA는 크게 다음 네 가지 요소를 중심으로 구성된다.</p>
<p><strong>State:</strong> 기능이 로직을 수행하고 UI를 그리는 데 필요한 <strong>상태를 정의</strong>한다.
<strong>Action:</strong> 사용자의 입력, 시스템 이벤트 등 기능에서 발생할 수 있는 모든 <strong>이벤트</strong>를 나타낸다.
<strong>Reducer:</strong> Action에 따라 State가 어떻게 <strong>변경</strong>되는지를 정의하는 역할을 한다.
<strong>Store:</strong> State와 Action을 연결하고, 실제로 <strong>기능을 실행</strong>하는 런타임 환경이다.</p>
<p>라고 간단한 설명을 할 수 있지만
프로젝트에 적용하기 위해선 흐름을 알아야 하기 때문에 Flow와 함께 보겠다.</p>
<p><img src="https://velog.velcdn.com/images/finda-tech/post/88076c3c-c109-4595-b95a-f689279b0f98/image.png" alt="">
<span style="color: gray;">출처: <a href="%5Bhttps://velog.io/@keonheehan/Swift-TCA.1-TCA%EC%9D%98-%EC%9D%B4%ED%95%B4%5D(https://velog.io/@keonheehan/Swift-TCA.1-TCA%EC%9D%98-%EC%9D%B4%ED%95%B4)">[Swift] TCA (The Composable Architecture)</a></span></p>
<p>그럼 이제 이 Flow를 기준으로 흐름을 하나하나 파헤쳐보겠다.</p>
<h4 id="view-→-action">View → Action</h4>
<p>Action은 그냥 간단하게 화면에서 사용자가 발생시키는 event라고 생각하면 된다.
TCA에서는 사용자의 모든 Action에 대해 <strong>enum의 case로 정의</strong>를 해야 한다.
Action의 명칭에는 나름의 규칙이 있는데,<br>Button을 Tapped 했다 처럼 기술되어야 한다. (이건 나중에 문서화에 적어두어야 할 듯)</p>
<p>원래 그냥 SwiftUI라면 
버튼을 눌러 State를 View에서 직접 바꾸어 줬겠지만, TCA는 좀 다르다.
직접적으로 바꾸지 않고 엄격하게 <strong>단방향으로 관리하기 위한 Reducer</strong>가 존재한다.</p>
<h4 id="action-→-reducer-→-state">Action → Reducer → State</h4>
<p>State는 그냥 말대로 UI의 상태인데
이제 그걸 관리하기 위해 State 값을 <strong>State 구조체로 관리</strong>한다.
그 구조체 안에 상태 변수가 선언되어 있겠지? 
그 변수의 값이 변하면, View를 다시 그린다고 생각하면 편하다.</p>
<pre><code>@Reducer
struct Feature {
    struct State: Equatable { // State는 무조건 Equatable 프로토콜을 준수해야 함
        var count = 0
    }

    enum Action: Equatable {
        case plusButtonTap
        case minusButtonTap
    }

    func reduce(into state: inout State, action: Action) -&gt; Effect&lt;Action&gt; {
        switch action {
        case .plusButtonTap:
            // 더하기 작업 구현
            state.count += 1
            return .none
        case .minusButtonTap:
            // 빼기 작업 구현
            state.count -= 1
            return .none
        }
    }
}</code></pre><p><span style="color: gray;">출처: <a href="https://ukseung2.tistory.com/entry/iOSSwiftUI-TCA%EB%9E%80-TCA%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B0%84%EB%8B%A8-%EC%98%88%EC%A0%9C">[iOS][SwiftUI] TCA란? TCA를 활용한 간단 예제</a></span></p>
<p>TCA에서 Reducer 하나의 단위를 Feature라는 접미사를 사용한다 .
<span style="color: gray;">(ex. <code>struct AllCategoryListFeature: Reducer {}</code> 또는 <code>@Reducer struct AllCategoryListFeature {}</code> )</span></p>
<p>Redcuer는 State와 Action을 인자로 받아 
action으로 State의 count를 변경하는 것을 확인할 수 있다.</p>
<p>여기서 <strong>Reduce는 Effect를 반환</strong>하는데 (위에 return .none) 그건 아래서 살펴보겠다.</p>
<h4 id="reducer-→-effect-→-action">Reducer → Effect → Action</h4>
<p>TCA에서는 Action에 의해 State 변경 뿐만 아니라 <strong>Side Effect</strong>를 발생시킬 수 있다.
<span style="color: gray;">(ex. 좋아요 버튼이 클릭 -&gt; 서버에 상태값을 전달 -&gt; UI에 이 변경사항의 성공 여부를 반영)</span></p>
<p>Effect는 다시 Action에 반영되고 
이 Action이 별다른 side effect가 없다면 return으로 .none을 반환하고 
해당 Action에 맞는 State를 변경하거나 비즈니스로직을 수행하면 된다.</p>
<p><strong>즉 Effect는 비동기 작업을 수행한 뒤, 
그 결과를 다시 Action으로 변환해 Reducer로 되돌린다.</strong></p>
<p>자 이제 View → Action → Reducer → State, Effect → Action을 했으니 
Reducer을 View와 연결해야 된다.</p>
<h4 id="store">Store</h4>
<p>Store은 앱의 <strong>상태를 보관하고 관리</strong>하는 핵심 객체이며,
State와 Action을 연결하는 Reducer를 <strong>View와 연결</strong>하는 역할을 한다.</p>
<pre><code>let store: StoreOf&lt;Feature&gt;

var body: some View {
    // 직접 store를 사용
    Text(&quot;\(store.count)&quot;)
    Button(&quot;Plus&quot;) {
        store.send(.plusButtonTap)
    }
}</code></pre><p>Store는 보통 부모가 생성해서 자식에게 전달한다. 
자식 View는 전달받은 Store를 통해 State를 읽고 Action을 전달할 뿐, 
직접 Store를 생성하지 않는다.</p>
<p><span style="color: gray;">참고
구버전(TCA 1.0 이전)에서는 <code>ViewStore</code>를 별도로 사용했으나, 
현재는 <code>store</code>를 직접 사용하는 방식으로 간소화되었다.</span></p>
<h4 id="dependency">Dependency</h4>
<p>다 끝난줄 알았지만, 플로우에 Dependency가 남아있다..</p>
<p>Dependency는 Client라고도 하는데 
이를 통해 네트워크 요청, 데이터베이스 작업, 또는 기타 외부 서비스와의 상호작용을 캡슐화하고 테스트 가능한 형태로 만들 수 있다.</p>
<p>TCA에서는 외부로 부터 전달 받는 값에 대해 
<strong>의존성을 낮추기</strong> 위해 Dependency라는 유용한 기능을 제공한다.</p>
<p>그냥 Dependency를 정의하고 사용하는곳에서 dependency의 메소드 사용하라는데..
이 부분이 정말 어려웠다.</p>
<p>보통은 Protocol를 이용한다고 알고있다. (struct도 이용 가능)</p>
<p>쉬운 이해를 위해 지피티한테 예시 코드를 요청했다.
버튼 누르면 서버 대신 가짜 API에서 숫자를 받아와서 화면에 보여주는 구조의 예시다.</p>
<pre><code>struct CounterClient {
    var fetchCount: () async throws -&gt; Int
}</code></pre><p>이렇게 먼저 프로토콜을 설계한다.</p>
<pre><code>import ComposableArchitecture

extension CounterClient: DependencyKey {
    static let liveValue = CounterClient(
        fetchCount: {
            // 실제 서버 대신 가짜 값 원래 여기에 api 호출 메소드 구현해야 함
            try await Task.sleep(for: .seconds(1))
            return Int.random(in: 0...100)
        }
    )
}</code></pre><pre><code>extension DependencyValues {
    var counterClient: CounterClient {
        get { self[CounterClient.self] }
        set { self[CounterClient.self] = newValue }
    }
}</code></pre><p><a href="https://matdongsane.tistory.com/193">SwiftUI) TCA에서의 의존성 주입, dependency</a> 블로그랑 유사한 코드다.
이렇게 안하고 fetchCount를 함수로 정의하여 struct로 하나하나 정의한 후에  DependencyKey할 때</p>
<p><code>static let liveValue: any CounterClient = struct</code> </p>
<p>방식으로도 하던데 그게 더 깔끔해 보였다.</p>
<p><a href="https://green1229.tistory.com/485">TCA - Dependency 설계</a> 이 블로그가 그런 방식을 사용한다.
이건 직접 코드를 짜보고 결정해봐야 할 것 같다.</p>
<hr>
<h3 id="tca에-대한-나의-생각">TCA에 대한 나의 생각</h3>
<p>TCA를 학습하면서 한 가지 의문이 들었다.</p>
<p><strong>TCA는 정말 아키텍처일까?</strong></p>
<p>이름에는 분명 &#39;Architecture&#39;가 들어가지만, 
실제로는 아키텍처보다는 패턴에 가깝다는 생각이 든다.</p>
<p>MVVM과 비교되는 것만 봐도 그렇다. MVVM이 패턴으로 분류되듯, TCA 역시 State 관리와 단방향 데이터 흐름을 강제하는 구체적인 구현 패턴에 가깝다고 느꼈다.</p>
<p>Clean Architecture나 레이어드 아키텍처처럼 전체 애플리케이션의 구조와 계층을 정의하는 것과는 결이 다르다. TCA는 Reducer, Effect, Dependency 등 매우 구체적인 구현 방식을 제시한다.</p>
<p>이러한 나의 생각을 다른 개발자분들과 공유하고 싶다.</p>
<hr>
<p>끝까지 읽어주셔서 감사합니다. 🙇‍♀️
글에 대한 피드백은 언제나 환영입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BE] Gradle 버전 충돌로 인한 build.gradle.kts 문제]]></title>
            <link>https://velog.io/@finda-tech/Gradle-%EB%B2%84%EC%A0%84-%EC%B6%A9%EB%8F%8C%EB%A1%9C-%EC%9D%B8%ED%95%9C-build.gradle.kts-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@finda-tech/Gradle-%EB%B2%84%EC%A0%84-%EC%B6%A9%EB%8F%8C%EB%A1%9C-%EC%9D%B8%ED%95%9C-build.gradle.kts-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 07 Jan 2026 14:23:17 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하기에-앞서">시작하기에 앞서</h3>
<p>안녕하세요.
대덕소프트웨어마이스터고등학교 2학년 <strong>Team Finda Backend</strong>의 변도휘, 박지민입니다.</p>
<p>지난 글에 이어 이번 글에서는 <strong>백엔드 개발 초기 프로젝트 세팅 과정에서 발생한 트러블 슈팅 경험</strong>을 공유하려고 합니다.</p>
<hr>
<h3 id="1-문제-상황">1. 문제 상황</h3>
<p><img src="https://velog.velcdn.com/images/finda-tech/post/74a4bac6-55ba-4f41-a180-7655ec9d1f74/image.png" alt=""></p>
<p>코틀린 프로젝트를 생성하자마자 build.gradle.kts 파일에서 에러가 발생했습니다.</p>
<p>처음에는 제 개발 환경 세팅 문제라고 생각했지만 다른 팀원들의 컴퓨터에서도 동일한 현상이 나타났습니다.</p>
<p>재밌게도 터미널에서 ./gradlew clean build 명령은 정상적으로 실행되었지만
IntelliJ IDE에서는 build.gradle.kts 파일이 빨간색으로 표시되며 오류가 발생했습니다... 🤯</p>
<hr>
<h3 id="첫-번째-시도">첫 번째 시도</h3>
<p>기존 build.gradle.kts 설정을 확인했더니 정식 안정 버전이 아닌 릴리즈가 되지 않은 버전을 사용하고 있었습니다.</p>
<p>이는 인텔리제이 Generator가 <strong>최신을 우선으로 노출하는 경향</strong> 때문에 발생한 것입니다.</p>
<pre><code>plugins {
    kotlin(&quot;jvm&quot;) version &quot;2.2.21&quot;
    kotlin(&quot;plugin.spring&quot;) version &quot;2.2.21&quot;
    id(&quot;org.springframework.boot&quot;) version &quot;4.0.1&quot;  // 아직 릴리즈 안됨
}
</code></pre><p>이를 아래와 같이 정상 릴리즈 버전으로 수정했습니다.</p>
<pre><code>plugins {
    kotlin(&quot;jvm&quot;) version &quot;1.9.25&quot;
    kotlin(&quot;plugin.spring&quot;) version &quot;1.9.25&quot;
    id(&quot;org.springframework.boot&quot;) version &quot;3.2.1&quot;
}
</code></pre><p>그러나 실행해보니 또 다른 오류가 발생했습니다.</p>
<pre><code>FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task &#39;:bootJar&#39;.
&gt; &#39;java.lang.Integer org.gradle.api.file.CopyProcessingSpec.getDirMode()&#39;
</code></pre><p>이 에러는 Gradle 9.2.1과 Spring Boot 3.2.1의 호환성 문제 때문이었습니다.</p>
<hr>
<h3 id="두-번째-시도">두 번째 시도</h3>
<p>gradle/wrapper/gradle-wrapper.properties 파일을 열어 Gradle 버전을 낮추었습니다.</p>
<pre><code># 변경 전
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip

# 변경 후
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
</code></pre><p>그리고 아래 명령어로 Gradle을 다시 적용했습니다.</p>
<pre><code>./gradlew wrapper --gradle-version 8.5
./gradlew --stop
./gradlew clean build</code></pre><p>마지막으로 IntelliJ에서 File &gt; Invalidate Caches / Restart &gt; Invalidate and Restart를 눌러 캐시를 초기화하고 IDE를 재시작했습니다.</p>
<hr>
<h3 id="성공🥳">성공!🥳</h3>
<p>그 결과 이제 build.gradle.kts 파일의 오류가 사라지고 IDE에서도 정상적으로 인식되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/finda-tech/post/a79e36c2-f167-4981-a16c-740ffd2e249d/image.png" alt=""></p>
<hr>
<h3 id="후기">후기</h3>
<p>처음에는 IntelliJ의 Generators로 생성한 프로젝트였기 때문에 별다른 수정 없이 갑자기 오류가 나서 꽤 당황스러웠습니다 😰</p>
<p>문제를 살펴보니 Gradle, Kotlin, Spring Boot 간 버전 충돌이 원인이었고
최신 Gradle 버전을 사용할 때는 플러그인과의 호환성에 민감하다는 점을 배우게 되었습니다.</p>
<p>앞으로는 프로젝트를 생성한 뒤 Gradle, Kotlin, Spring Boot, JDK 버전 조합을 먼저 확인하고 시작해야겠다는 교훈을 얻었습니다.</p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[BE] DDD 설계를 이렇게 해봤는데요...]]></title>
            <link>https://velog.io/@finda-tech/DDD-%EC%84%A4%EA%B3%84%EB%A5%BC-%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%95%B4%EB%B4%A4%EB%8A%94%EB%8D%B0%EC%9A%94</link>
            <guid>https://velog.io/@finda-tech/DDD-%EC%84%A4%EA%B3%84%EB%A5%BC-%EC%9D%B4%EB%A0%87%EA%B2%8C-%ED%95%B4%EB%B4%A4%EB%8A%94%EB%8D%B0%EC%9A%94</guid>
            <pubDate>Fri, 19 Dec 2025 11:21:24 GMT</pubDate>
            <description><![CDATA[<h3 id="시작하기에-앞서">시작하기에 앞서</h3>
<p>안녕하세요.
대덕소프트웨어마이스터고등학교 2학년 <strong>Team Finda Backend</strong>의 변도휘, 박지민입니다.</p>
<p>본 글은 저희가 개발을 진행하기에 앞서 설계한 내용에 대해
조언과 피드백을 받고자 작성하게 되었습니다.
부족한 점이나 개선할 부분이 있다면 편하게 말씀해 주시면 감사하겠습니다.</p>
<hr>
<h3 id="1-프로젝트-설명">1. 프로젝트 설명</h3>
<p><strong>Finda</strong>는 학교 봉사 활동을 효율적으로 관리할 수 있도록 돕는 <strong>웹·앱 기반 통합 플랫폼</strong>입니다.</p>
<p>선생님과 학생 모두 로그인하여 서비스를 이용할 수 있으며,선생님은 봉사 활동을 생성하고 학생들의 신청을 관리할 수 있습니다.
또한 봉사 시간을 조회·부여하고, 알림 전송 및 <strong>QR 코드를 활용한 출석 확인</strong> 기능을 사용할 수 있습니다.</p>
<p>학생들은 과거 봉사 활동을 조회하고 신청할 수 있으며, 마이페이지에서 유저의 정보와 누적 봉사 시간을 확인할 수 있습니다. 또한 선생님의 QR 코드를 스캔하여 간편하게 출석 체크를 할 수 있습니다.</p>
<p>선생님과 학생 모두 <strong>캘린더 기능</strong>을 통해 봉사 일정을 한눈에 확인할 수 있으며,
앱에서는 탭바의 <strong>Calendar 아이콘</strong>, 웹에서는 메인 페이지의 <strong>봉사 일정 확인하기 버튼</strong>을 통해 접근할 수 있습니다.</p>
<p>Finda는 봉사 활동 관리 과정을 디지털화하여 운영 효율을 높이고, 학생들의 봉사 참여를 보다 쉽고 적극적으로 만들어주는 서비스입니다.</p>
<hr>
<h3 id="2-이전-상황">2. 이전 상황</h3>
<p>학교에서 기본적으로 DDD를 사용하고 있지만, DDD를 깊게 공부하기 전에 먼저 코드로 접했습니다. 그 결과 DDD에 대한 충분한 이해 없이 코드를 작성해 왔습니다.</p>
<p>따라서 Finda에서는 설계와 개발을 시작하기에 앞서서, 코드에 어떻게 DDD를 적용할지 <strong>제대로 학습하고 이해한 뒤에 개발을 진행할 것입니다.</strong></p>
<hr>
<h3 id="3-학습-계획">3. 학습 계획</h3>
<p>먼저 반 버논이 작성한 <strong>도메인 주도 설계 핵심(DDD)</strong>을 함께 읽고 도메인을 설계해보기로 했습니다. </p>
<p><img src="https://velog.velcdn.com/images/finda-tech/post/46148974-7aa9-437d-8d60-4cd54f40071e/image.png" alt=""></p>
<p><strong><span style="color: gray;">책 정리 글은 너무 길어질 것 같아서, 다른 게시글로 게시하겠습니다.</span></strong></p>
<hr>
<h3 id="4-ddd-설명">4. DDD 설명</h3>
<p>이해를 위해 간단하게 DDD를 설명하고 넘어가겠습니다.</p>
<p>DDD에서는 <strong>도메인</strong>이라는 용어가 매우 자주 언급됩니다.
먼저 DDD에서 설명하는 도메인이란, <strong>비즈니스가 실제로 돌아가는 규칙과 개념, 그리고 그에 대한 지식의 집합</strong>을 의미합니다.</p>
<p>이러한 관점에서 DDD는 소프트웨어 설계에서 도메인 지식, 즉 비즈니스가 동작하는 규칙에 중심을 두는 소프트웨어 개발론이라고 할 수 있습니다. DDD에서는 기술적인 요소보다 도메인 지식이 가장 중요하게 다뤄집니다.</p>
<p>이를 위해 개발자는 도메인 전문가와 협력하며, 이 과정에서 유비쿼터스 언어와 같은 공통의 언어를 사용합니다. 또한 도메인 모델은 비즈니스 요구사항을 정확하게 반영하도록 설계됩니다. 이러한 특징으로 인해 <strong>DDD는 비즈니스와 기술 간의 차이를 최대한 줄이려는 설계 방식을 지향</strong>합니다.</p>
<hr>
<h3 id="5-바운디드-컨텍스트의-정의">5. 바운디드 컨텍스트의 정의</h3>
<p><strong>의미적으로 동일한 컨텍스트</strong>의 범위에 해당합니다.
해당 범주 안에서 소프트웨어 모델의 각 컴포넌는 특정한 의미를 갖고, 특정한 일을 수행합니다. </p>
<p>핀다에서는 세 개의 바운디드 컨텍스트를 두었습니다. </p>
<hr>
<h3 id="6-finda에서-분리한-바운디드-컨텍스트">6. Finda에서 분리한 바운디드 컨텍스트</h3>
<p><strong>알림 바운디드 컨텍스트</strong></p>
<ul>
<li>알림 전송 바운디드 컨텍스트입니다.</li>
</ul>
<p><strong>봉사 바운디드 컨텍스트</strong></p>
<ul>
<li>봉사 신청 및 관리 컨텍스트으로 <strong>핵심 도메인</strong>에 속합니다.</li>
</ul>
<p><strong>사용자 관리 바운디드 컨텍스트</strong></p>
<ul>
<li>인증/인가 처리 및 사용자 관리 컨텍스트입니다.</li>
</ul>
<hr>
<h3 id="7-이렇게-분리한-이유">7. 이렇게 분리한 이유</h3>
<p>Finda에서는 시스템 간 결합도를 효과적으로 낮추기 위해
서로 다른 책임과 변경 특성을 가진 영역을 기준으로 바운디드 컨텍스트를 분리하였습니다.</p>
<p>봉사, 인증/인가(Auth), 알림은 모두 사용자와 연관된 기능이지만,
각각 담당하는 역할과 내부 규칙, 그리고 변경이 발생하는 이유가 명확히 다릅니다.
이러한 영역을 하나의 컨텍스트로 묶을 경우,
<strong>특정 기능의 변경이 다른 영역까지 불필요하게 영향</strong>을 미칠 수 있습니다.</p>
<p>먼저 <strong>봉사 컨텍스트</strong>는 신청, 승인, 출석, 완료와 같은 상태 전이 규칙을 중심으로 하는
<strong>서비스의 핵심 비즈니스 영역</strong>입니다.
이 영역은 학교 운영 정책 변화에 따라 규칙이 자주 변경될 수 있으므로,
다른 관심사로부터 독립적으로 보호될 필요가 있습니다.</p>
<p>반면 <strong>Auth 컨텍스트</strong>는 로그인, 토큰, 권한과 같은 보안 중심의 책임을 가지며,
인증 방식이나 권한 모델 변경과 같은 보안 정책 변화가 주요 변경 요인입니다.</p>
<p>또한 <strong>알림 컨텍스트</strong>는 메시지 템플릿, 채널, 재시도와 같은
전달 정책과 신뢰성이 핵심인 영역입니다.
메시지 정책 변경이 핵심 도메인에 영향을 주지 않도록,
봉사 도메인은 알림을 직접 호출하지 않고
봉사 신청 수락/거절 등과 같은 도메인 이벤트를 통해 간접적으로 연결됩니다.</p>
<hr>
<h3 id="8-조언-부탁드립니다-🙇♀️">8. 조언 부탁드립니다 🙇‍♀️</h3>
<p>기존에는 DDD를 제대로 이해하지 못한 상태에서 개발을 진행했었기에
기초부터 다시 공부하며 적용 중입니다.</p>
<p>혹시 보시기에
“이건 좀 아닌데…”
싶은 부분이 있다면
조금만 시간 내서 한마디 남겨주시면 정말 감사하겠습니다. 🙇‍♀️</p>
<p>아직 많이 부족한 단계라
따끔한 조언도 감사히 듣겠습니다…!</p>
<p>긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
    </channel>
</rss>