<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>squee_z_e.log</title>
        <link>https://velog.io/</link>
        <description>멋있는 사람이 되는 게 꿈입니다</description>
        <lastBuildDate>Mon, 18 May 2026 08:06:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. squee_z_e.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/squee_z_e" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Oracle] 의료·바이오 AI Service Architect]]></title>
            <link>https://velog.io/@squee_z_e/Oracle-%EC%9D%98%EB%A3%8C%EB%B0%94%EC%9D%B4%EC%98%A4-AI-Service-Architect</link>
            <guid>https://velog.io/@squee_z_e/Oracle-%EC%9D%98%EB%A3%8C%EB%B0%94%EC%9D%B4%EC%98%A4-AI-Service-Architect</guid>
            <pubDate>Mon, 18 May 2026 08:06:28 GMT</pubDate>
            <description><![CDATA[<h3 id="sesac-ai-academy">SeSAC AI+ Academy?</h3>
<p><a href="https://aisesac.seoul.kr/sesac">SeSAC AI+ Academy</a> 라는 게 신설됐다.
서울시, 대한상공회의소, 새싹, 빅테크 기업들이 함께 AI 중점 교육을 진행한다.</p>
<h3 id="oracle-교육과정">Oracle 교육과정</h3>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/339c757a-a42f-4a86-9644-f4cad4a84e2a/image.png" alt=""></p>
<p><a href="https://aisesac.seoul.kr/shop_view?idx=14">교육과정</a> 설명이 원래 길게 나왔는데 모집 종료되면서 자세한 부분은 삭제된 것 같다.</p>
<p>5/10 2차 모집 마감
5/11 서류 합격 발표
5/13 (오전) 면접
5/14 (오후) 최종합격
5/15 등록 마감
5/18~9/10 수업 + 프로젝트
10월 중에 해커톤 예정</p>
<p>사실 빨리 취업을 해야하는데 또 교육에 참여하는 게 맞나 고민했지만,
내 현 상황에서 플랜 B는 필요할 것 같아 참여 의사를 확정했다.
믿습니다 오라클</p>
<pre><code>[장점]
- 기업이 직접 교육 (교육 주관사가 다르지 않음)
- 지원 - 면접 - 수업참여 - 취업지원 일정 매우 빠름
- 취업연계? 취업지원? 제공
- 오라클 자격증 응시비용 제공 + SQLD(5/31) 준비할 때 시너지 날 것 같음
- DB, 클라우드, AI 쪽 지식 확장 + 의료/바이오 쪽 도메인 확장
- 점심 제공, 실습용 노트북 대여

[단점]
- 교육만 너무 많이 듣는 게 오히려 약점이 될수도
- 비개발자도 참여 가능해서 교육 난이도가 애매함, 이미 경험한 내용도 있음
- AI 겉핥기의 효용성? 의료/바이오?
</code></pre><p>교육 들으면서 자격증도 준비하고 가능하면 TIL을 좀 올려볼 예정</p>
<p>동작캠퍼스 첫 기수라는 듯??
대방역 서울가족플라자 안에 있다.
다른 새싹 캠퍼스에 비해 규모가 크진 않은 것 같지만 시설은 괜찮다.
다만 주변에 뭐가 없다.. 그래서 식대 대신 도시락 제공해주시는 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/bbd73609-0283-4253-815d-d07bcbd8331a/image.jpg" alt=""></p>
<p>웰컴키트를 받았다!</p>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/a7ae6efb-34e0-43b0-b70e-ca1b587d19a4/image.jpg" alt=""></p>
<p>hp 게이밍 노트북을 대여받았다. sLLM 돌아가야해서 GPU 달린 걸로 주신 듯 하다. 코덱스 세팅해서 쉬는 시간에 사이드 프젝도 돌려봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 12/03 데이터 저장은 어떡하지]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251203</link>
            <guid>https://velog.io/@squee_z_e/TIL-251203</guid>
            <pubDate>Wed, 03 Dec 2025 03:11:54 GMT</pubDate>
            <description><![CDATA[<h3 id="로컬-db-vs-디스크-저장">로컬 DB vs 디스크 저장</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th><strong>로컬 DB (Drift / Hive / Isar)</strong></th>
<th><strong>디스크 저장(JSON 등 파일)</strong></th>
</tr>
</thead>
<tbody><tr>
<td>구조</td>
<td>테이블/컬렉션(구조적)</td>
<td>파일 단위(비정형 가능)</td>
</tr>
<tr>
<td>장점</td>
<td>검색/정렬/필터링 강함</td>
<td>포맷 자유, 레포트·로그에 적합</td>
</tr>
<tr>
<td>단점</td>
<td>스키마·마이그레이션 필요</td>
<td>검색·부분 업데이트 비효율적</td>
</tr>
<tr>
<td>사용 목적</td>
<td>자주 조회/수정되는 데이터</td>
<td>변경 적고 “기록을 남기는” 데이터</td>
</tr>
<tr>
<td>실제 사례</td>
<td>채팅, 앱 캐시, 사용자 정보</td>
<td>로그, JSON 백업, PDF</td>
</tr>
</tbody></table>
<ul>
<li>DB = 데이터를 자주 읽고 관리하기 위한 것</li>
<li>디스크 저장 = 파일을 보존하거나 내보내기 위한 것</li>
</ul>
<h3 id="어떻게-저장할거임">어떻게 저장할거임?</h3>
<table>
<thead>
<tr>
<th>저장 방식</th>
<th>특징</th>
<th>장점</th>
<th>단점</th>
<th>주요 사용처</th>
</tr>
</thead>
<tbody><tr>
<td><strong>메모리 상태(Cubit/Provider 등)</strong></td>
<td>앱 실행 중에만 유지</td>
<td>가장 빠른 접근, 구조 단순</td>
<td>앱 종료 시 소멸</td>
<td>UI 임시 상태, 선택값 등</td>
</tr>
<tr>
<td><strong>SharedPreferences</strong></td>
<td>Key–Value 설정 저장</td>
<td>간단한 사용성, 가벼운 플래그 저장에 적합</td>
<td>복잡 구조 저장 어려움, 암호화 없음</td>
<td>다크모드, 온보딩 여부, 간단 설정</td>
</tr>
<tr>
<td><strong>flutter_secure_storage</strong></td>
<td>OS 보안 저장소 사용</td>
<td>암호화, 민감 정보 저장 적합</td>
<td>느린 I/O, 대량 저장 부적합</td>
<td>액세스 토큰, 인증 정보</td>
</tr>
<tr>
<td><strong>파일 저장(dart:io)</strong></td>
<td>JSON/텍스트/바이너리 파일 저장</td>
<td>자유도 높음, 로그/백업 저장 적합</td>
<td>직렬화 직접 처리, 동시성 고려 필요</td>
<td>로그, 레포트, 백업 데이터</td>
</tr>
<tr>
<td><strong>SQLite (sqflite)</strong></td>
<td>관계형 데이터베이스</td>
<td>복잡 쿼리/조인 가능, 검증된 안정성</td>
<td>SQL 직접 작성, 마이그레이션 부담</td>
<td>오프라인 DB, 검색/정렬이 많은 데이터</td>
</tr>
<tr>
<td><strong>Drift</strong></td>
<td>SQLite 기반 타입 안전 ORM</td>
<td>타입 안전 쿼리, 구조적 DB 설계 용이</td>
<td>러닝커브, 코드 생성 필요</td>
<td>로컬 데이터 구조가 복잡한 앱, 오프라인 동기화</td>
</tr>
<tr>
<td><strong>Hive</strong></td>
<td>키–값 기반 NoSQL</td>
<td>매우 빠른 속도, 모델 직렬화 지원</td>
<td>복잡 쿼리/조인 불가</td>
<td>캐시, 간단한 도메인 저장, 스냅샷</td>
</tr>
<tr>
<td><strong>Isar</strong></td>
<td>고성능 로컬 NoSQL DB</td>
<td>빠른 성능, 인덱스/필터 지원</td>
<td>마이그레이션 학습 필요</td>
<td>대량 로컬 데이터, 검색/필터 기능</td>
</tr>
</tbody></table>
<ul>
<li>머리에 넣어두자
앱 재시작 필요 없음 (일시 상태) → 메모리 상태
간단한 설정값 → SharedPreferences
민감 정보 → flutter_secure_storage
레포트·로그·백업 → 디스크 저장(JSON/PDF)
구조적 데이터(검색·정렬·통계)→ Drift or SQLite
캐시·간단 저장 → Hive or Isar</li>
<li>실제로는 혼용해야지
반복적으로 조회·필터링해야 하는 구조적 데이터는 Drift/Hive
레포트/기록성 데이터는 파일 저장(JSON 또는 이진 파일)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 12/02 추상화 니가 뭔데]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251202</link>
            <guid>https://velog.io/@squee_z_e/TIL-251202</guid>
            <pubDate>Wed, 03 Dec 2025 02:14:22 GMT</pubDate>
            <description><![CDATA[<h2 id="진행-상황">진행 상황</h2>
<hr>
<h3 id="1-모델을-freezed-기반으로-통일">1. 모델을 freezed 기반으로 통일</h3>
<h3 id="핵심-포인트">핵심 포인트</h3>
<ul>
<li>Freezed는 <strong>불변성(immutability)</strong> 확보</li>
<li><code>copyWith</code>, equality, toString 자동 생성</li>
<li>JSON 직렬화는 <code>json_serializable</code>로 자동 처리</li>
<li>enum 직렬화는 <code>JsonConverter</code>로 관리</li>
</ul>
<h4 id="개선-이유">개선 이유</h4>
<ul>
<li>모델 필드 변경 시 JSON 변환 코드를 직접 수정해야 하는 문제 해소</li>
<li>타입 안정성 확보</li>
<li>전체 모델 구조를 일관적으로 유지하기 용이</li>
</ul>
<h4 id="freezed-도입-효과">Freezed 도입 효과</h4>
<ul>
<li>생성자 + 직렬화 코드 자동화</li>
<li>state 관리 안정성 상승</li>
<li>유지보수 비용 감소</li>
</ul>
<hr>
<h3 id="2-json-직렬화--역직렬화-흐름-정리">2. JSON 직렬화 / 역직렬화 흐름 정리</h3>
<h4 id="역직렬화json-→-객체">역직렬화(JSON → 객체)</h4>
<ul>
<li>Dio가 JSON 문자열 → Map 변환</li>
<li>Retrofit이 응답 타입을 보고 <code>fromJson</code> 호출</li>
<li>json_serializable이 생성한 <code>_$ModelFromJson</code> 실행</li>
<li>Freezed 내부의 구현체로 모델 생성</li>
<li>최종적으로 Dart 객체로 전달</li>
</ul>
<h4 id="직렬화객체-→-json">직렬화(객체 → JSON)</h4>
<ul>
<li>Dio가 객체에 <code>toJson()</code> 존재 여부 확인</li>
<li>Freezed + json_serializable이 생성한 <code>_$ModelToJson</code> 실행</li>
<li>Map → JSON 문자열 변환 후 API 요청</li>
</ul>
<h4 id="핵심-요약">핵심 요약</h4>
<ul>
<li>Retrofit은 타입 기반으로 자동 직렬화</li>
<li>Freezed는 모델 구조를 일관적으로 유지</li>
<li>json_serializable은 실제 변환 로직 담당</li>
</ul>
<hr>
<h3 id="3-repository--datasource-추상화">3. Repository / DataSource 추상화</h3>
<h4 id="문제점">문제점</h4>
<ul>
<li>DataSource가 단일 구현체로 고정</li>
<li>테스트용 Mock과 실제 API 교체가 어려움</li>
<li>Repository가 구현체에 직접 결합됨</li>
</ul>
<h4 id="개선-방향">개선 방향</h4>
<ul>
<li><code>abstract DataSource</code> 정의</li>
<li>API 구현체, Mock 구현체를 별도 분리</li>
<li>Repository는 인터페이스에만 의존</li>
<li>main.dart에서 의존성 주입으로 구현체 교체</li>
</ul>
<h4 id="구조-요약">구조 요약</h4>
<pre><code>Cubit → Repository(인터페이스) → DataSource(인터페이스) → 구현체(API 또는 Mock)</code></pre><h4 id="장점">장점</h4>
<ul>
<li>테스트 쉬움</li>
<li>교체 유연성 확보</li>
<li>의존성 역전(DIP) 적용</li>
<li>단일 책임(SRP) 유지</li>
</ul>
<h4 id="단점">단점</h4>
<ul>
<li>파일 증가</li>
<li>작은 프로젝트에서 과한 구조 가능</li>
</ul>
<hr>
<h3 id="4-전체-아키텍처-레이어-맵">4. 전체 아키텍처 레이어 맵</h3>
<pre><code>UI
  ↓
Cubit (State 관리)
  ↓
Repository (비즈니스 로직)
  ↓
DataSource (API, Mock)
  ↓
Model (Freezed)</code></pre><h4 id="핵심-원칙">핵심 원칙</h4>
<ul>
<li>상태는 불변성 기반</li>
<li>비즈니스 로직과 데이터 접근 분리</li>
<li>인터페이스 기반 의존</li>
<li>자동 코드 생성 활용</li>
</ul>
<hr>
<h2 id="진행-예정">진행 예정</h2>
<hr>
<h3 id="1-앱이-종료돼도-데이터가-유지되게">1. 앱이 종료돼도 데이터가 유지되게</h3>
<ul>
<li><strong>디스크 저장 방식</strong>이나 <strong>클라이언트 DB(Hive/Drift)</strong> 사용 고려</li>
<li>클라이언트 DB를 적극적으로 활용하는 앱 사례가 많으며,
우리도 앱 고도화 단계에서 <strong>변동성이 낮은 데이터(예: 리포트 등)를 로컬에 캐싱</strong>해 전달하는 방식으로 활용 가능</li>
</ul>
<blockquote>
<p>웹페이지 리뉴얼은 참여 안 할수도..?</p>
</blockquote>
<blockquote>
<p>나중에 reactiveX, 추상화, 디자인 패턴, flutter에서의 상태 관리와 타이밍 이슈 처리 전략 같은 부분을 좀 더 개념적이고 기술적인 부분 위주로 학습해서 포스팅 해야겠다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 12/01 오래도 걸렸다]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251201</link>
            <guid>https://velog.io/@squee_z_e/TIL-251201</guid>
            <pubDate>Mon, 01 Dec 2025 08:18:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>11/28<del>주말 조금</del>12/01 진행 내용</p>
</blockquote>
<h2 id="진행-상황">진행 상황</h2>
<h3 id="1-상태-관리-구조-개선-sealed-class-→-freezed-전환">1. 상태 관리 구조 개선: Sealed Class → Freezed 전환</h3>
<p>상태 관리를 기존의 수동 sealed class 방식에서 Freezed 기반 구조로 정리함.
특히 화면 성격별로 <strong>플래그 기반 단일 State</strong>와 <strong>유니온 타입 State</strong>를 구분하여 적용함.</p>
<h4 id="mainscreen-플래그-기반-단일-state로-정리">MainScreen: 플래그 기반 단일 State로 정리</h4>
<p>메인 화면은 동시에 여러 데이터를 들고 있고, 부분 업데이트가 잦음.
따라서 여러 클래스로 나누기보다 하나의 State 안에 플래그로 묶는 방식이 적합하다고 판단함.</p>
<pre><code class="language-dart">@freezed
class MainScreenState with _$MainScreenState {
  const factory MainScreenState({
    @Default(UserRegistrationData()) UserRegistrationData userData,
    @Default(&lt;Pet&gt;[]) List&lt;Pet&gt; pets,
    @Default(false) bool isLoading,
    String? error,
  }) = _MainScreenState;
}</code></pre>
<p>이 구조는 다음과 같은 장점을 가짐:</p>
<ul>
<li><code>copyWith()</code>로 필요한 부분만 갱신하기 쉬움</li>
<li>UI는 <code>if (state.isLoading)</code> 같은 단순 조건으로 분기함</li>
<li>등록 정보 + 펫 정보처럼 <strong>서로 다른 리소스를 동시에 관리하는 화면</strong>에 적합함</li>
</ul>
<h4 id="userregistration--petregistration-유니온-타입-유지">UserRegistration / PetRegistration: 유니온 타입 유지</h4>
<p>등록 플로우는 단계가 명확한 구조라 유니온 타입이 더 자연스러움.
각 단계가 “명시적으로 표현되는” 것이 중요하다고 판단해 Freezed의 union type을 그대로 유지함.</p>
<pre><code class="language-dart">@freezed
class PetRegistrationState with _$PetRegistrationState {
  const factory PetRegistrationState.initial(Pet pet) = PetRegistrationInitial;
  const factory PetRegistrationState.loaded(Pet pet) = PetRegistrationLoaded;
  const factory PetRegistrationState.loading() = PetRegistrationLoading;
  const factory PetRegistrationState.success(Pet pet) = PetRegistrationSuccess;
  const factory PetRegistrationState.failure(String message) = PetRegistrationFailure;
}</code></pre>
<p>유니온 타입의 장점은 다음과 같음:</p>
<ul>
<li><code>when</code>, <code>map</code>으로 <strong>모든 상태를 반드시 처리</strong>하게 되어 실수 방지</li>
<li>각 상태별로 필요한 데이터만 들고 있게 설계 가능함</li>
<li>단계가 명확한 “등록/입력 흐름”에 최적화되어 있음</li>
</ul>
<p><strong>정리:</strong></p>
<ul>
<li><strong>메인 화면 → 플래그 기반 단일 State</strong></li>
<li><strong>등록 플로우 → Freezed 유니온 타입</strong>
두 방식을 상황에 맞게 병행함.</li>
</ul>
<hr>
<h3 id="2-reactivex-패턴-적용-behaviorsubject--switchmap">2. ReactiveX 패턴 적용: BehaviorSubject + switchMap</h3>
<p>변경 사항 자동 반영, 요청 버전 관리, 타이밍 문제 해결을 위해 Repository 레이어에 ReactiveX 패턴을 도입함.</p>
<h4 id="behaviorsubject를-repository에-추가함">BehaviorSubject를 Repository에 추가함</h4>
<p>각 Repository에서 <code>BehaviorSubject&lt;String?&gt;</code>를 사용해 “어떤 유저의 데이터가 변경됐는지”를 스트림 형태로 발행함.</p>
<pre><code class="language-dart">final _userUpdateSubject = BehaviorSubject&lt;String?&gt;.seeded(null);

Stream&lt;String?&gt; get userUpdates =&gt; _userUpdateSubject.stream;

Future&lt;void&gt; registerUser(String userId, UserRegistrationData data) async {
  await dataSource.registerUser(userId, data);
  _userUpdateSubject.add(userId);
}</code></pre>
<p>BehaviorSubject의 핵심 특징은 다음과 같음:</p>
<ul>
<li>“마지막 emit 값”을 기억함</li>
<li>새로운 구독자가 붙으면 그 값을 즉시 emit함</li>
<li>Cubit이 늦게 구독해도 최신 데이터를 놓치지 않음</li>
</ul>
<h4 id="mainscreencubit에서-switchmap으로-요청-버전-관리함">MainScreenCubit에서 switchMap으로 요청 버전 관리함</h4>
<p>MainScreenCubit은 userUpdates 스트림을 구독하며,
<strong>switchMap을 이용해 이전 API 요청을 취소하고 마지막 요청만 처리하는 방식</strong>을 적용함.</p>
<pre><code class="language-dart">_userUpdateSubscription = userRegistrationRepository.userUpdates
    .where((userId) =&gt; userId != null &amp;&amp; _currentUserId == userId)
    .switchMap(
      (userId) =&gt; Stream.fromFuture(
        userRegistrationRepository.getUserDataByUserId(userId!),
      ),
    )
    .listen(
      (userData) {
        emit(state.copyWith(
          userData: userData ?? state.userData,
          error: null,
        ));
      },
    );</code></pre>
<p>이 방식의 장점은 다음과 같음:</p>
<ul>
<li>연속으로 빠르게 업데이트가 들어와도 <strong>마지막 이벤트만 유효</strong></li>
<li>오래 걸린 응답이 늦게 도착해 UI를 덮어쓰는 문제 방지</li>
<li>BehaviorSubject와 조합하면 “초기 로딩 + 자동 새로고침”이 자연스럽게 연결됨</li>
</ul>
<p>요약하면,
<strong>BehaviorSubject는 최신 이벤트 보장, switchMap은 요청 버전 관리</strong>
이 두 가지가 결합해 타이밍 이슈 전반이 해결됨.</p>
<hr>
<h3 id="3-fastapi-로컬-서버-구축">3. FastAPI 로컬 서버 구축</h3>
<p>FastAPI로 직접 로컬 서버를 구축해 실제 API 구조와 더 유사한 환경을 구성함.</p>
<h4 id="인메모리-db-구조-설계">인메모리 DB 구조 설계</h4>
<ul>
<li>Python 딕셔너리로 users, pets 저장</li>
<li><code>user_id_counter</code>, <code>pet_id_counter</code>로 ID 자동 증가</li>
<li>login ID와 내부 시스템 ID를 별도로 관리</li>
</ul>
<p>이 구조로 Flutter 측의 <code>loginUserId</code>와 서버 내부 primary key를 명확히 분리할 수 있었음.</p>
<h4 id="환경-구성">환경 구성</h4>
<p>FastAPI에서 CORS 설정 후 Flutter에서 바로 호출하도록 구성했고,
Flutter 앱에서는 환경 변수를 다음과 같이 주입하는 방식으로 관리함.</p>
<pre><code class="language-bash">flutter run --dart-define=API_BASE_URL=http://192.168.0.2:3000</code></pre>
<p>환경별 서버 주소를 유연하게 바꿀 수 있는 구조로 개선함.</p>
<hr>
<h3 id="4-retrofit--dio로-flutter-api-클라이언트-정리">4. Retrofit + Dio로 Flutter API 클라이언트 정리</h3>
<p>FastAPI와 통신하는 Flutter 클라이언트를 Retrofit + Dio 기반으로 재구성함.</p>
<ul>
<li><code>@RestApi()</code>, <code>@GET</code>, <code>@POST</code>로 인터페이스만 작성</li>
<li>JSON 직렬화와 실제 HTTP 요청은 자동 생성됨</li>
<li><code>build_runner</code>로 <code>*.g.dart</code>, <code>*.freezed.dart</code> 파일 생성</li>
</ul>
<p>일관된 API 모델과 상태 모델을 유지할 수 있고, 404 응답을 에러가 아닌 “데이터 없음”으로 처리하는 식의 흐름 제어가 용이함.</p>
<pre><code class="language-dart">if (e is DioException &amp;&amp; e.response?.statusCode == 404) {
  return null;
}</code></pre>
<p>등록 정보가 없을 때는 비정상 상황이 아니므로 이렇게 처리함.</p>
<hr>
<h3 id="5-로그인-및-초기-로딩-처리-개선">5. 로그인 및 초기 로딩 처리 개선</h3>
<h4 id="listenwhen으로-불필요한-listener-호출-차단">listenWhen으로 불필요한 listener 호출 차단</h4>
<pre><code class="language-dart">listenWhen: (previous, current) {
  return previous.user?.id != current.user?.id;
},</code></pre>
<ul>
<li>user.id가 실제로 바뀔 때만 listener가 실행되도록 구성함</li>
<li>로딩 플래그 변화나 사소한 state 변경에는 반응하지 않도록 최적화함</li>
</ul>
<h4 id="초기-진입-시-데이터가-비어-보이는-문제-해결">초기 진입 시 데이터가 비어 보이는 문제 해결</h4>
<p>BlocListener는 첫 빌드 때 동작하지 않기 때문에,
필요한 경우 <code>addPostFrameCallback</code>으로 초기 <code>loadData</code>를 강제 실행하도록 구성함.</p>
<pre><code class="language-dart">if (!state.isLoading &amp;&amp;
    state.userData.nickname == null &amp;&amp;
    state.pets.isEmpty &amp;&amp;
    loginState.loginUserId != null) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    context.read&lt;MainScreenCubit&gt;().loadData(loginState.loginUserId!);
  });
}</code></pre>
<p>BehaviorSubject의 캐싱과 결합되면서
“초기 진입 / 로그인 변경 / 등록 완료 후 복귀 등 모든 타이밍에서 최신 상태 유지”가 가능해짐.</p>
<hr>
<blockquote>
<p>여기서부터 팀장님 피드백</p>
</blockquote>
<h2 id="진행-예정">진행 예정</h2>
<h3 id="1-repositorydatasource-추상화">1. Repository–DataSource 추상화</h3>
<p>현재 DataSource가 단일 구현체라 Repository가 구체 클래스에 직접 결합된 구조.
테스트용 목업과 실제 API를 동일한 흐름으로 교체하려면 DataSource를 <strong>인터페이스로 추상화</strong>하는 구조가 필요.</p>
<p><strong>개선 방향 요약</strong></p>
<ul>
<li><code>abstract UserRegistrationDataSource</code> 정의</li>
<li>Mock / API 구현체 분리</li>
<li>Repository는 인터페이스만 의존</li>
<li>저장 방식 변경 시에도 앱 흐름 유지</li>
</ul>
<hr>
<h3 id="2-freezed-state를-flag-기반으로-통일">2. Freezed State를 Flag 기반으로 통일</h3>
<p>MainScreen은 flag 기반 단일 state인데,
UserRegistration / PetRegistration은 union type 구성.</p>
<p>등록 플로우는 단계가 많지 않고 전이가 단순하므로,
MainScreen과 동일하게 <strong>단일 state + flag 기반</strong>이 더 적합한 구조.</p>
<p><strong>예시 형태</strong></p>
<pre><code class="language-dart">@freezed
class PetRegistrationState with _$PetRegistrationState {
  const factory PetRegistrationState({
    @Default(Pet.empty()) Pet pet,
    @Default(false) bool isLoading,
    String? error,
  }) = _PetRegistrationState;
}</code></pre>
<p><strong>핵심 개념</strong>
Freezed는 <strong>상태 변경 지점을 제한</strong>하는 목적.
state는 불변이며, 모든 변경은 <code>copyWith</code> 기반으로만 생성.
상태 추적, 디버깅, 변경 히스토리 파악이 쉬운 구조.</p>
<hr>
<h3 id="3-모델userregistrationdata--pet을-freezed로-전환">3. 모델(UserRegistrationData / Pet)을 Freezed로 전환</h3>
<p>현재 모델은</p>
<ul>
<li>직접 작성한 <code>fromJson</code> / <code>toJson</code></li>
<li><code>Equatable</code> 기반 비교
형태로 구성.</li>
</ul>
<p>필드 추가·변경 시 JSON 변환 코드도 매번 직접 수정해야 하는 구조라 유지보수 비용 증가.</p>
<p><strong>개선 방향</strong>
모델을 Freezed + json_serializable 기반으로 재구성.</p>
<p><strong>기대 효과 요약</strong></p>
<ul>
<li>JSON 직렬화 자동화</li>
<li>copyWith, equality, 불변성 자동 생성</li>
<li>타입 구조 일관성 확보</li>
<li>코드량 감소</li>
</ul>
<hr>
<blockquote>
<p>이번 리팩까지 하고나면 플러터 학습 마무리하고 앱 실제 구조 살펴볼 예정</p>
</blockquote>
<blockquote>
<p>서비스 리뉴얼 참여(약 1주)해서 프레임워크 안쓰고 순수 웹 3대장으로 UI 고치고 배포하는 과정에서 병목 등의 문제 해결..?</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/27 확실히 말끔해져 가는 코드]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251127</link>
            <guid>https://velog.io/@squee_z_e/TIL-251127</guid>
            <pubDate>Thu, 27 Nov 2025 10:11:51 GMT</pubDate>
            <description><![CDATA[<h3 id="느끼는-건데">느끼는 건데..</h3>
<p>제발 java랑 javascript 공부 좀 하자.. C python 해서 뭐할래
객체지향 프로그래밍도 더 깊게 공부해야 할 것 같다.</p>
<p>일에 바로 투입되지는 못하고.. 공부만 하고 있는 게 좀 죄송했다.
모두가 엄청 바쁘게 일 하는 상황에서 나만 1인분을 못하고 있었기 때문이다.
&#39;로그인-앱 등록-펫 등록&#39; 이 짧은 흐름 하나를 구현해놓고
피드백을 받아가며 여러 방식으로 고쳐나갔는데 뭘 하고 있는 건지 티도 안났다.</p>
<p><strong>근데? 오늘 처음 구현했던 코드를 보고 지금 코드를 보니 
실무 바로 들어가서 AI 써재꼈으면 진짜 큰일났겠다 싶었다!</strong></p>
<p>일단 하루 30분 할애해서 코드 보면서 피드백 해주시고..
어떤 식으로 생각해야 하는지, 어떤 패키지들을 적용해볼 수 있는지 하나하나 짚어주시는 게
사실 자원을 엄청 쓰고 있다는 걸 알고 있어서 공부를 열심히 하려곤 했다.
근데 베이스가 너무 부족해서 힘들고 학습이 되긴 하는 건지 몰랐는데
<strong>좋은 코드가 어떤 건지 보는 눈과 짜임새 있게 코드를 구성하는 사고력이 생겨가는 것 같다.</strong></p>
<p>물론 지금도 코드 쓰는 걸 버벅이고 생각의 흐름이 매끄럽지 못해 AI에게 많이 배우고 있지만..
진짜 뭣도 모르면서 AI에게 거의 모든 걸 맡겼던 내가 얼마나 잘못된 방식을 선택했는지 깨닫는다.</p>
<p>주니어 개발자 내 자신 화이팅...</p>
<hr>
<h3 id="오늘-한-내용">오늘 한 내용</h3>
<h4 id="1-레이어-구조-정리">1. 레이어 구조 정리</h4>
<ul>
<li>전체 구조를 <strong>UI → Cubit → Repository → DataSource</strong> 로 명확히 분리</li>
<li>UI에서 Repository를 직접 호출하던 부분 제거</li>
<li>Cubit이 비즈니스 로직과 데이터 접근을 전담하도록 구조 개선
→ 상태관리·의존성·테스트 용이성이 크게 향상됨</li>
</ul>
<h4 id="2-상태-클래스-개선">2. 상태 클래스 개선</h4>
<ul>
<li>모든 상태 클래스를 <code>sealed class + final class</code>로 재구성</li>
<li><code>switch</code> 표현식 사용 가능</li>
<li>모든 상태 분기를 <strong>컴파일 타임에 강제</strong>할 수 있어 안정성 확보</li>
</ul>
<h4 id="3-gorouter-실험">3. GoRouter 실험</h4>
<ul>
<li><strong>pageBuilder 방식</strong>: 부모 페이지(Page)가 실제로 생성되는 구조</li>
<li><strong>redirect 방식</strong>: 부모 페이지 없이 자식 경로로 곧바로 이동</li>
<li>AI는 redirect 방식이 URL 구조·뒤로가기 스택에 유리하다는데 뭐가 더 좋은지는..</li>
</ul>
<h4 id="4-mainscreen-구조-정비">4. MainScreen 구조 정비</h4>
<ul>
<li>메인 화면을 <code>StatefulWidget → StatelessWidget</code> 으로 변경</li>
<li>상태·데이터 로드를 담당하는 <strong>MainScreenCubit 신설</strong></li>
<li>로그인 상태 변화에 따라 Cubit이 사용자·펫 데이터를 자동 재로딩
→ UI는 “상태만 읽는 구조”로 단순화됨</li>
</ul>
<h4 id="5-자동-새로고침-로직-정비">5. 자동 새로고침 로직 정비</h4>
<p>1) 수동 refresh 방식 사용해봄</p>
<ul>
<li>등록 완료 후 UI 쪽에서 <code>refreshUser / refreshPets</code> 직접 호출</li>
<li>print 디버깅으로 호출 시점·rebuild 문제 확인</li>
</ul>
<p>2) Stream 기반 자동 동기화 사용해봄</p>
<ul>
<li>Repository에 <code>StreamController.broadcast()</code> 추가</li>
<li>데이터 변경 시 <code>stream.add(userId)</code>로 이벤트 발행</li>
<li>MainScreenCubit이 stream을 <code>listen()</code>하여
같은 <code>userId</code>일 때 자동으로 <code>refreshUser / refreshPets</code> 실행</li>
<li>구독은 <code>StreamSubscription</code>으로 관리, Cubit close에서 취소
→ 수동 refresh 불필요, 화면 자동 동기화 완성</li>
</ul>
<h4 id="6-사용자-정보-수정-플로우">6. 사용자 정보 수정 플로우</h4>
<ul>
<li>메인 화면에 유저 정보 수정 버튼 추가</li>
<li>기존 등록 플로우(닉네임 등 입력 화면)를 <strong>수정 모드</strong>로 재사용</li>
<li>기존 데이터가 TextField에 자동 세팅</li>
<li>수정 완료 시 stream 이벤트 → 메인 자동 새로고침</li>
</ul>
<h4 id="7-디버깅-및-개념-정리">7. 디버깅 및 개념 정리</h4>
<ul>
<li>Repository stream과 흐름 이해</li>
<li>Repository를 전역 Provider로 두는 이유:<ul>
<li>의존성 주입</li>
<li>Stream 공유</li>
<li>테스트 및 Mock 용이</li>
</ul>
</li>
</ul>
<hr>
<h3 id="피드백-받은-내용-한-번에-정리">피드백 받은 내용 한 번에 정리</h3>
<h4 id="1-로딩실패-상태-처리">1. <strong>로딩/실패 상태 처리</strong></h4>
<ul>
<li>새 데이터가 도착했을 때 Cubit이 로딩 중이거나 실패 상태라면 기존 데이터를 어떻게 유지/대체할지 타이밍을 고민해야 함</li>
</ul>
<h4 id="2-reactivexsubject-개념">2. <strong>ReactiveX/Subject 개념</strong></h4>
<ul>
<li>기본 Stream 외에 <code>BehaviorSubject</code> 같은 RX 개념도 존재. 마지막 emit 값을 캐싱해 두고 새 구독자가 즉시 현재 상태를 알 수 있어 UI 초기화에 유리</li>
</ul>
<h4 id="3-sealed-class-대안">3. <strong>sealed class 대안</strong></h4>
<ul>
<li>sealed class를 쓰지 않고 <code>MainState.loading</code>, <code>MainState.success</code>처럼 상태를 한 객체 안에 묶는 패턴도 있음  </li>
<li>더 immutable하게 만들고 싶다면 <code>freezed</code> 같은 코드 생성 패키지를 검토</li>
</ul>
<h4 id="4-실제-api-콜-테스트">4. <strong>실제 API 콜 테스트</strong></h4>
<ul>
<li>실제 REST API를 호출하는 플로우도 한번 테스트해 보자</li>
<li>간단한 서버를 띄우거나, 테스트용 HTTP 서비스를 이용해 보고, Retrofit 같은 REST 클라이언트도 경험해 보라고 권장</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/26 디버깅을 제대로 하자]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251126</link>
            <guid>https://velog.io/@squee_z_e/TIL-251126</guid>
            <pubDate>Wed, 26 Nov 2025 11:23:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>한 것 
go router 사용 라우트 관리, cubit provider 주입 시점 조절,  repo provider 사용</li>
</ul>
</blockquote>
<ul>
<li>못 한 것
provider 내부 동작 살펴보기, sealed class 사용</li>
<li>과제는 아니었지만 이상하게 만들어서 수정이 시급한 것
레이어 분리 제대로(UI가 레포 호출 바로 못하게 - cubit이 처리)</li>
</ul>
<p>어찌저찌했는데 머리에 다 들어온 건 아닌 듯</p>
<hr>
<blockquote>
<p>여기서부터 팀장님 피드백</p>
</blockquote>
<hr>
<h3 id="cubit-분리-전략">Cubit 분리 전략</h3>
<h4 id="cubit을-따로-만들지-않는-경우">Cubit을 따로 만들지 않는 경우</h4>
<p>등록 플로우 Cubit과 전역 리스트 Cubit을 분리하지 않고 하나로 운용할 때 두 방식 중 선택</p>
<h4 id="방식-1-파라미터--리프레시-호출">방식 1: 파라미터 + 리프레시 호출</h4>
<ul>
<li>업데이트가 필요한 화면으로 파라미터 전달</li>
<li>변경 시 리프레시 신호를 보내서 해당 화면이 데이터 다시 로드</li>
<li>장점: 구조 단순</li>
<li>단점: 수동 리프레시 필요, 여러 화면 동기화 어렵다</li>
<li>사용 시기: 단순 플로우</li>
</ul>
<h4 id="방식-2-repository-스트림-구독">방식 2: Repository 스트림 구독</h4>
<ul>
<li>Repository에 스트림을 두고 데이터 변경 시 이벤트 발행</li>
<li>Cubit 등이 스트림을 구독해 자동으로 상태 갱신</li>
<li>장점: 자동 동기화, 화면 여러 개여도 일관성 유지</li>
<li>단점: 구조 복잡</li>
<li>사용 시기: 데이터 공유가 많고 흐름이 복잡할 때</li>
</ul>
<p><strong>선택 기준</strong></p>
<ul>
<li>단순: 방식 1</li>
<li>복잡·다중 화면: 방식 2</li>
</ul>
<hr>
<h3 id="디버깅-습관">디버깅 습관</h3>
<h4 id="의식의-흐름을-제대로">의식의 흐름을 제대로</h4>
<ol>
<li>갱신이 필요한 위치 확인</li>
<li>갱신을 호출하는 코드 위치 확인</li>
<li>호출은 되었는지, 수신은 되었는지, 수행은 되었는지 로그로 확인</li>
</ol>
<p>이런게 자연스럽게 이뤄져야 함</p>
<h4 id="로그-활용-잘하기">로그 활용 잘하기</h4>
<p>에러 떴으면 뭐라 떴는 지 좀 잘 보기</p>
<hr>
<h3 id="go_router-메서드-요약">go_router 메서드 요약</h3>
<h4 id="contextgopath"><code>context.go(path)</code></h4>
<ul>
<li>스택 전체 대체</li>
<li>뒤로가기 불가</li>
<li>로그인 성공 후 메인 이동 등</li>
</ul>
<h4 id="contextpushpath"><code>context.push(path)</code></h4>
<ul>
<li>스택에 화면 추가</li>
<li>뒤로가기 가능</li>
<li>pop 결과값(Future) 받을 수 있음</li>
</ul>
<h4 id="contextpopresult"><code>context.pop([result])</code></h4>
<ul>
<li>현재 화면 제거</li>
<li>push로 온 화면에서만 가능</li>
<li>이전 화면으로 결과 전달 가능</li>
</ul>
<h4 id="contextreplacepath"><code>context.replace(path)</code></h4>
<ul>
<li>현재 화면만 교체</li>
<li>아래 스택 유지</li>
<li>뒤로가기 가능</li>
</ul>
<h4 id="contextcanpop"><code>context.canPop()</code></h4>
<ul>
<li>pop 가능 여부 확인</li>
</ul>
<h4 id="contextgonamedname-"><code>context.goNamed(name, …)</code></h4>
<ul>
<li>라우트 이름 기반 go</li>
<li>동작은 go와 동일</li>
</ul>
<h4 id="contextpushnamedname-"><code>context.pushNamed(name, …)</code></h4>
<ul>
<li>라우트 이름 기반 push</li>
<li>동작은 push와 동일</li>
</ul>
<h4 id="contextreplacenamedname-"><code>context.replaceNamed(name, …)</code></h4>
<ul>
<li>라우트 이름 기반 replace</li>
<li>동작은 replace와 동일</li>
</ul>
<hr>
<h3 id="ui-→-repository-직접-호출-금지">UI → Repository 직접 호출 금지</h3>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/f1aaf2ec-a8d4-45a5-befe-7eba2d6db90b/image.png" alt=""></p>
<p>레이어는 제대로 짜놓고 왜 ui가 cubit 부르는거랑 repo    부르는 걸 섞어놨니..</p>
<h4 id="잘못된-구조-ui-→-repository-직접-호출">잘못된 구조 (UI → Repository 직접 호출)</h4>
<pre><code class="language-dart">onPressed: () async {
  await context.read&lt;PetRepository&gt;().registerPet(userId, pet);
}</code></pre>
<h4 id="올바른-구조-ui-→-cubit-→-repository">올바른 구조 (UI → Cubit → Repository)</h4>
<pre><code class="language-dart">onPressed: () {
  context.read&lt;PetListCubit&gt;().addPet(userId, pet);
}

class PetListCubit extends Cubit&lt;PetListState&gt; {
  Future&lt;void&gt; addPet(String userId, Pet pet) async {
    await repository.registerPet(userId, pet);
    emit(PetListLoaded([...pets, pet]));
  }
}</code></pre>
<h4 id="이유">이유</h4>
<ul>
<li>비즈니스 로직은 Cubit에 집중</li>
<li>UI는 “상태와 이벤트”만 알고 Repository는 Cubit만 알도록 분리</li>
<li>테스트 용이성</li>
<li>상태 흐름 일관성 유지</li>
</ul>
<p>핵심: <strong>UI는 Cubit만 호출, Cubit이 Repository를 호출하는 구조</strong></p>
<hr>
<h3 id="goroute--redirect--builder--pagebuilder-정리">GoRoute : redirect / builder / pageBuilder 정리</h3>
<h4 id="redirect">redirect</h4>
<ul>
<li>라우팅 직전에 실행되어 <strong>경로만 변경하는 함수</strong></li>
<li>UI나 페이지를 만들지 않고 단순히 이동만 처리</li>
<li>인증 체크, 초기 플로우 분기 같은 상황에서 사용</li>
<li>부모 페이지 없이 바로 다른 경로로 이동하는 구조</li>
</ul>
<h4 id="builder">builder</h4>
<ul>
<li>child 라우트를 <strong>부모 위젯으로 감싸는 방식</strong></li>
<li>반환값이 Widget이기 때문에 Navigator 스택에는 올라가지 않음</li>
<li>공통 Scaffold, AppBar, BlocProvider 같은 “레이아웃 래핑”에 사용</li>
<li>화면 구조는 부모 위젯이 존재하지만 “페이지 단위”는 아님</li>
</ul>
<h4 id="pagebuilder">pageBuilder</h4>
<ul>
<li><strong>Navigator가 사용하는 실제 Page(MaterialPage 등)</strong>를 생성</li>
<li>부모 Page가 스택에 올라가고, 그 안에서 child가 보여지는 구조</li>
<li>뒤로가기 히스토리에도 부모 페이지가 포함됨</li>
<li>페이지 단위 공통 레이아웃이 필요할 때 사용</li>
</ul>
<h4 id="요약">요약</h4>
<ul>
<li><strong>redirect</strong>: 경로만 변경, UI 없음</li>
<li><strong>builder</strong>: 부모 위젯 생성, Page는 아님</li>
<li><strong>pageBuilder</strong>: 부모 Page 생성, 스택에 올라감</li>
</ul>
<hr>
<blockquote>
<ul>
<li>레이어 제대로 고치고 디버깅 제대로 해서 내가 못고쳤던 문제 해결해보기</li>
</ul>
</blockquote>
<ul>
<li>go_route에서 리다이렉트말고 페이지 빌더 사용해보기</li>
<li>스트림 구독 vs 리프레시 주기</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/25 cubit으로 상태 관리를 해봤다]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251125</link>
            <guid>https://velog.io/@squee_z_e/TIL-251125</guid>
            <pubDate>Tue, 25 Nov 2025 10:23:39 GMT</pubDate>
            <description><![CDATA[<h3 id="cubit이란">Cubit이란</h3>
<ul>
<li>Bloc 라이브러리에서 제공하는 <strong>단순 상태관리 클래스</strong></li>
<li>메서드를 통해 상태 변경 → <code>emit()</code> 호출</li>
<li>UI는 Cubit의 상태 스트림을 구독해 화면을 갱신함</li>
</ul>
<hr>
<h3 id="cubit-기본-구조">Cubit 기본 구조</h3>
<ul>
<li><strong>State</strong>: 불변 객체, 여러 상태 클래스로 표현</li>
<li><strong>Cubit 클래스</strong>: 초기 상태 설정 → 메서드에서 <code>emit()</code> 호출</li>
<li>비동기/동기 모두 가능</li>
</ul>
<hr>
<h3 id="ui와-cubit-연결">UI와 Cubit 연결</h3>
<ul>
<li><strong>UI → Cubit</strong>: <code>context.read&lt;Cubit&gt;().method()</code></li>
<li><strong>Cubit → UI</strong>: 상태 스트림 전달</li>
<li><strong>BlocBuilder</strong>: 상태 기반으로 UI 빌드</li>
<li><strong>BlocListener</strong>: 네비게이션·팝업 등 부수 효과 처리</li>
<li><strong>BlocConsumer</strong>: builder + listener 동시 사용</li>
</ul>
<hr>
<h3 id="read--watch--select">read / watch / select</h3>
<ul>
<li><strong>read()</strong>: Cubit 인스턴스 가져오기 (액션 호출용), 리빌드 없음</li>
<li><strong>watch()</strong>: Cubit 상태 전체 구독, 리빌드 발생</li>
<li><strong>select()</strong>: 특정 필드만 구독, 리빌드 최적화</li>
</ul>
<hr>
<h3 id="cubit-동작-원리">Cubit 동작 원리</h3>
<ul>
<li><strong>단방향 흐름</strong>: <code>UI → Cubit → State → UI</code></li>
<li><strong>emit()</strong>: 새 상태를 스트림으로 내보냄</li>
<li><strong>상태는 반드시 새로운 객체</strong>로 생성될 것</li>
</ul>
<hr>
<h3 id="cubit-vs-bloc">Cubit vs Bloc</h3>
<ul>
<li><p><strong>공통</strong>: 단방향 데이터 흐름, 상태 기반 UI, 테스트 용이</p>
</li>
<li><p><strong>차이점</strong></p>
<ul>
<li>Cubit: 메서드 → emit, 코드량 적음, 단순 로직에 적합</li>
<li>Bloc: Event → Handler → emit, 복잡한 로직·플로우에 적합</li>
</ul>
</li>
</ul>
<hr>
<h3 id="언제-어떤-걸-쓰는가">언제 어떤 걸 쓰는가</h3>
<ul>
<li><strong>Cubit</strong>: 단일 화면, API 호출 + 로딩/성공 구조 등 대부분의 케이스</li>
<li><strong>Bloc</strong>: 상태 흐름 복잡, 다단계 이벤트, 상태머신이 필요한 기능</li>
</ul>
<hr>
<blockquote>
<p>여기부터는 팀장님 피드백 받은 내용들</p>
</blockquote>
<hr>
<h3 id="contextread--watch--select">context.read / watch / select</h3>
<ul>
<li><p>flutter_bloc이 BuildContext에 추가한 확장 함수</p>
</li>
<li><p><strong>read<T>()</strong> <code>context.read&lt;T&gt;()</code> </p>
<ul>
<li>Cubit/Bloc 인스턴스를 가져오기만 함</li>
<li>상태 변화와 무관, 리빌드 없음</li>
<li>버튼 클릭처럼 Cubit 메서드 실행할 때 사용</li>
</ul>
</li>
<li><p><strong>watch<T>()</strong> <code>context.watch&lt;T&gt;()</code></p>
<ul>
<li>Cubit 상태를 구독해 상태가 바뀔 때마다 위젯 리빌드</li>
<li>작은 위젯은 <code>context.watch&lt;Cubit&gt;().state</code>로 간단하게 사용</li>
</ul>
</li>
<li><p><strong>select()</strong> <code>context.select&lt;T, R&gt;(selector)</code></p>
<ul>
<li>상태의 특정 필드만 구독</li>
<li>selector 값이 바뀌지 않는 한 리빌드 없음 → 퍼포먼스 최적화</li>
</ul>
</li>
</ul>
<hr>
<h3 id="blocbuilder-vs-watch">BlocBuilder vs watch</h3>
<ul>
<li><strong>BlocBuilder</strong>: 어떤 Cubit을 구독하는지 명시적, 리빌드 범위 제어에 유리</li>
<li><strong>watch</strong>: 간단하게 상태를 읽고 싶을 때 편함</li>
<li>둘 다 Cubit 상태에 따라 UI를 다시 그림</li>
</ul>
<hr>
<h3 id="repository를-provider로-주입하자">Repository를 Provider로 주입하자</h3>
<ul>
<li><p>Repository를 Cubit 내부에서 직접 생성하는 대신
상위 위젯에서 Repository 인스턴스를 Provider로 만들어 하위에서 <code>context.read&lt;Repository&gt;()</code>로 가져와 사용</p>
</li>
<li><p>장점</p>
<ul>
<li>재사용성 증가</li>
<li>mock 교체 쉬워 테스트 편함</li>
<li>여러 Cubit이 동일한 Repository 공유 가능</li>
<li>의존성 주입 구조가 명확해짐</li>
</ul>
</li>
</ul>
<hr>
<h3 id="abstract-class-대신-sealed-class">abstract class 대신 sealed class</h3>
<ul>
<li>Dart 3에서 제공하는 sealed class는<ul>
<li>같은 파일 내부에서만 하위 타입 생성 가능</li>
<li>switch/case 문에서 누락된 상태가 있으면 컴파일러가 잡아줌</li>
<li>default 없이도 모든 상태를 안전하게 처리 가능</li>
</ul>
</li>
<li>Bloc/Cubit의 상태(State) 선언에 가장 안전한 방식</li>
</ul>
<hr>
<h3 id="repository를-직접-new-하지-말고-contextread로-주입하는-이유">Repository를 직접 new 하지 말고 <code>context.read()</code>로 주입하는 이유</h3>
<ul>
<li>Repository를 Cubit 안에서 직접 <code>new</code> 하면 Cubit이 특정 구현체에 묶여 결합도가 높아짐</li>
<li>테스트에서 Mock Repository로 교체하기 어렵고, 화면·플로우마다 다른 Repository를 쓰는 것도 불가능해짐</li>
<li>Provider는 위젯 트리 상단에서 Repository 인스턴스를 한 번만 만들어 두고, 하위에서는 <code>context.read&lt;Repository&gt;()</code>로 받아서 사용</li>
<li>Cubit은 생성 방식이나 구현체를 몰라도 되고, “필요한 의존성을 주입받아 쓰는” 구조가 됨</li>
<li>의존성 관리가 깔끔해지고 교체·모킹·재사용·라이프사이클 제어가 쉬워짐</li>
<li>결론적으로 Repository는 Provider가 만들고, Cubit은 read로 받아 쓰는 구조가 정석임</li>
</ul>
<hr>
<h3 id="go_router와-provider-주입-시점-조절">go_router와 Provider 주입 시점 조절</h3>
<ul>
<li>go_router는 라우트 단위로 화면을 구성하는 패키지</li>
<li>특정 플로우(회원가입, 결제 등)에서만 필요한 Cubit은 해당 라우트 내부에서 Provider로 생성</li>
<li>장점<ul>
<li>플로우 종료 시 Cubit이 자동 dispose</li>
<li>메모리 관리 깔끔</li>
<li>라우트 단위로 상태 수명 주기를 통제할 수 있음</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/24 provider를 이용해서 상태 관리를 해봤다]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251124</link>
            <guid>https://velog.io/@squee_z_e/TIL-251124</guid>
            <pubDate>Mon, 24 Nov 2025 09:50:40 GMT</pubDate>
            <description><![CDATA[<h3 id="flutter-상태관리-정리">Flutter 상태관리 정리</h3>
<p>Flutter에서는 화면을 구성하는 모든 요소가 위젯이며, 위젯들은 부모-자식 관계로 이루어진 트리 구조를 형성한다. 위젯은 불변이기 때문에, UI가 변경될 때는 기존 위젯을 수정하는 대신 새로운 위젯을 다시 그려주는 방식으로 동작한다.</p>
<hr>
<h3 id="statelesswidget과-statefulwidget">StatelessWidget과 StatefulWidget</h3>
<h4 id="공통점">공통점</h4>
<p>두 위젯 모두 <code>build(BuildContext)</code> 메서드 안에서 “이 위젯이 어떤 UI인지”를 선언한다.</p>
<h4 id="statelesswidget">StatelessWidget</h4>
<ul>
<li>내부에 변경되는 상태가 없다.</li>
<li><code>Text</code>, <code>Icon</code>, <code>Padding</code> 같은 정적인 UI에 사용된다.</li>
<li>동일한 입력이 주어지면 항상 동일한 결과를 반환하며, 부모 위젯이 재빌드될 때만 다시 그려진다.</li>
</ul>
<h4 id="statefulwidget">StatefulWidget</h4>
<ul>
<li>변경 가능한 상태를 가진다.</li>
<li><code>StatefulWidget</code>과 실제 상태를 보관하는 <code>State</code> 객체가 쌍으로 동작한다.</li>
<li><code>setState()</code>를 호출하면 해당 위젯만 다시 빌드되어 동적 UI를 만들 수 있다.</li>
<li>사용자 입력, 값 변화, 애니메이션 등 시간이 지나며 변하는 UI에 사용한다.</li>
</ul>
<hr>
<h3 id="상태state의-개념">상태(State)의 개념</h3>
<p>상태는 UI를 그리기 위해 필요한 데이터나 리소스를 의미한다. Flutter에서는 상태를 크게 두 가지로 나눈다.</p>
<h4 id="ephemeral-state-지역-상태">Ephemeral State (지역 상태)</h4>
<ul>
<li>한 위젯 내부에서만 잠시 필요한 상태</li>
<li>예: 체크박스 선택 여부, 특정 화면의 입력값</li>
<li><code>State + setState()</code>로 관리한다.</li>
</ul>
<h4 id="app-state-전역공유-상태">App State (전역/공유 상태)</h4>
<ul>
<li>여러 화면 또는 여러 위젯에서 함께 사용하는 상태</li>
<li>예: 로그인 정보, 장바구니, 앱 설정 값</li>
<li>InheritedWidget, Provider 등 상태관리 도구를 사용해 관리한다.</li>
</ul>
<hr>
<h3 id="주요-상태관리-방식-요약">주요 상태관리 방식 요약</h3>
<h4 id="statefulwidget--setstate">StatefulWidget + setState</h4>
<ul>
<li>간단한 로컬 상태에 적합</li>
<li>추가 패키지 없이 사용할 수 있고 이해하기 쉽다</li>
<li>하지만 공유 상태가 많아질수록 구조가 복잡해진다</li>
</ul>
<h4 id="inheritedwidget-계열-inheritednotifier--inheritedmodel">InheritedWidget 계열 (InheritedNotifier / InheritedModel)</h4>
<ul>
<li>트리 상단에서 하위 위젯들에게 상태를 전달하는 Flutter 기본 메커니즘</li>
<li>직접 활용하면 코드가 복잡해 주로 기반 개념으로만 사용된다</li>
</ul>
<h4 id="changenotifier--provider">ChangeNotifier + Provider</h4>
<ul>
<li>Flutter 공식이 초보자에게 추천하는 방식</li>
<li>ChangeNotifier 안에 상태를 보관하고 <code>notifyListeners()</code>로 변경을 알린다</li>
<li>Provider는 이 변경을 구독하고 필요한 위젯만 다시 빌드한다</li>
<li>전역 상태를 깔끔하게 관리할 수 있고, MultiProvider로 여러 상태를 함께 관리할 수 있다</li>
<li>상태가 커지면 ChangeNotifier가 비대해지는 단점이 있다</li>
</ul>
<h4 id="기타-상태-관리-패키지-bloc-riverpod-등">기타 상태 관리 패키지 (Bloc, Riverpod 등)</h4>
<ul>
<li>팀 규모나 프로젝트 복잡도에 따라 선택</li>
<li>구조적이고 테스트하기 쉬운 상태관리 패턴 제공</li>
<li>보일러플레이트가 줄거나, 더 명확한 아키텍처를 만들 수 있다</li>
<li>러닝 커브가 존재하고 패키지 의존성이 추가된다</li>
</ul>
<hr>
<h3 id="flutter-상태관리와-provider-그리고-repository-아키텍처-정리">Flutter 상태관리와 Provider, 그리고 Repository 아키텍처 정리</h3>
<p>Flutter에서 화면은 모두 위젯으로 이루어져 있으며, UI는 위젯 트리 형태로 그려진다. 위젯은 불변이기 때문에 상태가 변경되면 기존 위젯을 수정하는 것이 아니라 새로운 위젯을 다시 생성하는 방식으로 갱신된다. 이로 인해 상태가 필요 없는 UI는 <code>StatelessWidget</code>, 상태가 필요한 UI는 <code>StatefulWidget</code>으로 나뉘게 된다.</p>
<p><code>StatefulWidget</code>은 특정 위젯 내부에서만 유효한 “지역 상태(ephemeral state)”를 관리하기에는 충분하지만, 앱의 여러 화면에서 공유해야 하는 전역 상태를 관리하기에는 한계가 있다. setState는 오직 자신의 위젯 내부만 업데이트하기 때문에, 상태를 다른 화면에 전달하려면 각 단계마다 파라미터로 넘겨야 하고, 구조가 빠르게 복잡해진다.</p>
<p>이 문제를 해결하기 위해 Flutter에서 널리 사용하는 방식이 Provider다. Provider는 <code>ChangeNotifier</code> 기반으로 상태를 객체 하나에 모아두고, <code>notifyListeners()</code>를 호출했을 때 해당 객체를 구독하는 Consumer만 다시 빌드된다. 이 덕분에 부모 위젯이 데이터를 일일이 전달하지 않아도 되고, 화면 어디에서든 동일한 상태를 쉽게 접근하고 변경할 수 있다.</p>
<hr>
<blockquote>
<p>나는 provider로 전역적으로 상태관리가 가능하도록 해서 학습을 하고 있었는데, 
팀장님께서 실무에서 쓰는 상황들을 더 설명해주셨다.
아래 내용은 설명해주신걸 기억하기 위해 정리해둔 글이다.</p>
</blockquote>
<hr>
<h3 id="provider의-범위scope와-라이프사이클">Provider의 범위(Scope)와 라이프사이클</h3>
<p>Provider는 흔히 “전역 상태관리 도구”로 오해되지만, 실제로는 <strong>상태를 원하는 범위의 서브트리에만 주입할 수 있는 도구</strong>다.
즉, Provider는 반드시 루트에만 두라는 규칙이 없다.</p>
<p>예를 들어 앱 구조가 다음과 같다고 하자.</p>
<pre><code>MultiProvider
  └── App
        ├── Route A
        └── Route B</code></pre><p>Provider는 다음과 같은 범위 중 어디든 주입할 수 있다.</p>
<ul>
<li>App 위 (전역)</li>
<li>App 아래</li>
<li>Route A 트리 내부</li>
<li>Route B의 하위 위젯에서만 사용</li>
</ul>
<p>Provider는 <strong>자신이 주입된 위치에 따라 lifespan(생명주기)이 결정</strong>된다.
Provider가 트리에 붙어 있는 동안만 살아 있고, 해당 트리가 사라지면 Provider도 자동으로 dispose된다.</p>
<p>이 특징 때문에 실무에서는 다음과 같이 나누는 패턴이 일반적이다.</p>
<ul>
<li><p><strong>앱 전체에서 필요한 상태</strong></p>
<ul>
<li>예: 로그인한 User 정보, 현재 재생 중인 음악, 설정 값</li>
<li>→ 루트(MultiProvider) 또는 최상단에 주입</li>
</ul>
</li>
<li><p><strong>특정 플로우 안에서만 필요한 상태</strong></p>
<ul>
<li>예: 회원가입 단계별 입력값, 특정 페이지에서만 쓰는 임시 필터 값</li>
<li>→ 해당 Route 또는 해당 화면 트리에만 Provider 주입</li>
</ul>
</li>
</ul>
<p>이렇게 하면 “전역에 올릴 필요 없는 상태”를 전역에 올려서 메모리를 낭비하거나 잔존 데이터를 남기는 문제를 피할 수 있다.</p>
<hr>
<h3 id="provider-shadowing-가장-가까운-provider를-읽는다">Provider Shadowing: 가장 가까운 Provider를 읽는다</h3>
<p>같은 타입의 Provider가 여러 레벨에 있을 수도 있다.
이 경우 <code>Consumer&lt;T&gt;</code> 또는 <code>context.watch&lt;T&gt;()</code>는 <strong>트리 아래에서 위로 올라가며 가장 가까운 Provider</strong>를 읽는다.</p>
<p>즉,</p>
<ul>
<li>Consumer는 “이 값이 어디서 왔는지” 모른다.</li>
<li>단지 “내 트리에서 가장 가까운 Provider<T>”를 구독할 뿐이다.</li>
</ul>
<p>이 특징을 이용하면 다음과 같은 패턴도 가능하다.</p>
<ul>
<li>플레이어 상태는 전역 Provider에 두고</li>
<li>추천 페이지에서만 필요한 추천 알고리즘 상태는 추천 페이지 트리에 Provider를 두고</li>
<li>페이지를 벗어나면 추천 상태만 자연스럽게 dispose됨</li>
<li>하지만 전역 Provider에 있는 플레이어 상태는 유지됨</li>
</ul>
<p>실무에서 화면별 상태와 전역 상태를 자연스럽게 분리하는 데 매우 유용한 방식이다.</p>
<hr>
<h3 id="flutter에서도-repository-레이어가-필요한-이유">Flutter에서도 Repository 레이어가 필요한 이유</h3>
<p>상태관리와 별개로, Flutter의 아키텍처에서도 자바/Spring과 동일한 방식으로 Repository 패턴을 사용할 수 있다.</p>
<p>Repository의 목적은 명확하다.
** “UI나 비즈니스 로직이 데이터가 어디서, 어떻게 오는지 모르게 한다.&quot;**</p>
<p>Dart에는 interface 키워드는 없지만, <code>abstract class</code>를 통해 인터페이스와 동일한 역할을 만들 수 있다.
Cubit/Bloc은 Repository의 인터페이스만 의존하고, API/캐시/Mock 구현체는 뒤에서 교체 가능하다.</p>
<hr>
<h3 id="레이어-구조">레이어 구조</h3>
<p>여러 디자인 패턴들이 있지만 다 비슷비슷하다. 중요한건 인터페이스를 잘 만드는 것이다.
내가 참여할 파트의 구조는 이런 식으로 되어있다.</p>
<pre><code>Screen → Cubit/Bloc → Repository(인터페이스) → 구현체(API/캐시/Local)</code></pre><ul>
<li><strong>Screen</strong>
UI 렌더링, 사용자 입력을 Cubit/Bloc에게 전달</li>
<li><strong>Cubit/Bloc</strong>
화면 상태/비즈니스 로직 담당. Repository 인터페이스만 호출</li>
<li><strong>Repository (abstract class)</strong>
앱 관점의 “데이터 가져오기 API” 정의</li>
<li><strong>구현체(Impl)</strong>
실제 HTTP 요청, 로컬 DB, 캐시 등 상세한 데이터 접근 로직</li>
</ul>
<p>이 구조가 가지는 가장 큰 장점은 <strong>UI가 데이터 출처를 전혀 모른다는 점</strong>이다.
UI 입장에서는 단지 <code>getUser()</code>를 호출하는 것뿐이며,
그 뒤에서 실제 요청이 어떤 방식으로 이루어지는지는 Repository가 책임진다.</p>
<hr>
<h3 id="인터페이스를-잘-만드는-것이-핵심">인터페이스를 잘 만드는 것이 핵심</h3>
<p>Repository 패턴의 가치는 단순히 “코드 나누기”가 아니라,
<strong>UI가 비즈니스 의미만 이해하고 구현 세부 사항을 모르게 만드는 데 있다.</strong></p>
<p>Bad 예시:</p>
<pre><code class="language-dart">Future&lt;Response&gt; get(String url);</code></pre>
<p>Good 예시:</p>
<pre><code class="language-dart">abstract class MusicRepository {
  Future&lt;List&lt;Music&gt;&gt; getRecommended();
}</code></pre>
<p>이렇게 Repository 이름과 메서드가 “도메인 용어”를 표현해야
UI와 Cubit/Bloc이 데이터 구조 변경에 영향을 받지 않는다.</p>
<hr>
<h3 id="왜-repository-패턴을-써야-하냐">왜 Repository 패턴을 써야 하냐?</h3>
<ul>
<li>API 변경에도 UI 로직 수정 없음</li>
<li>테스트 시 Mock Repository로 손쉽게 대체 가능</li>
<li>로컬 캐시 + API + DB 같은 복합 데이터 소스 구성에 적합</li>
<li>화면 로직(Cubit/Bloc)이 깔끔해지고 유지보수성 증가</li>
<li>실무 앱 대부분이 이 방식으로 성장함</li>
</ul>
<p>즉, Flutter에서도 Repository 레이어는
데이터 로직을 독립시키고 UI·상태 로직을 깨끗하게 유지하는 핵심 아키텍처다.</p>
<blockquote>
<p>이제 cubit을 이용한 걸로 다시 한번 로그인-회원가입-ㅁㅁ 등록 흐름을 만들어볼거다.
Event → State 식으로 하면 복잡해지는 부분이? 있기 때문에 cubit만을 사용할 것 같다.
지금 일단 알려주신 단축키들 잘 활용해보자.
<code>cmd + .</code> : 전구기능 실행 / <code>option + shift + F</code> : 줄맞춤 / <code>Snippet + tap</code> : 자동 완성</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/20 flutter 도전기]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251120</link>
            <guid>https://velog.io/@squee_z_e/TIL-251120</guid>
            <pubDate>Thu, 20 Nov 2025 01:59:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>실무 배정을 앱 쪽으로 받게 되면서 flutter를 공부하게 됐다. 학습을 좀 깊게 해야할 듯</p>
</blockquote>
<p>개발환경 세팅은 <a href="https://www.youtube.com/watch?v=fBz3dlQ-EDM&amp;list=PLQt_pzi-LLfokCMjavyUqpAi_NdxVbgrq&amp;index=5">코딩 셰프의 mac 세팅 영상</a>을 따라하다가, <a href="https://docs.flutter.dev/get-started/quick?_gl=1*4amn84*_up*MQ..*_ga*ODg3OTM0MDkxLjE3NjM1MTM0ODY.*_ga_04YGWK0175*czE3NjM1MTM0ODUkbzEkZzAkdDE3NjM1MTM0ODUkajYwJGwwJGgw">공식 문서</a>보고 그냥 좀 더 쉬운 방식으로 했다.
막히는 건 GPT로 해결.. 경로 중간에 <strong>한글이 포함되지 않도록</strong> 미리 주의합시다</p>
<p><strong><a href="https://product.kyobobook.co.kr/detail/S000200473539">코드팩토리의 플러터 프로그래밍</a></strong> 책을 기반으로 개념을 학습하고 있다.</p>
<hr>
<h3 id="dart-언어의-장점">Dart 언어의 장점</h3>
<ul>
<li><strong>일관된 개발 경험</strong>: 모바일·웹·데스크톱을 하나의 언어로 개발</li>
<li><strong>빠른 개발 사이클</strong>: Hot Reload 지원</li>
<li><strong>타입 안정성</strong>: Null-safety 기반</li>
<li><strong>성능 최적화 용이</strong>: VM + AOT 조합으로 런타임 성능 확보</li>
<li><strong>UI 지향 언어 디자인</strong>: 비동기 처리·상태 관리에 자연스러운 문법</li>
</ul>
<h3 id="dart의-컴파일-방식jit--aot">Dart의 컴파일 방식(JIT / AOT)</h3>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/06755178-4112-4eaf-a74f-50ae24bb197f/image.png" alt=""></p>
<h4 id="jit-just-in-time">JIT (Just-In-Time)</h4>
<ul>
<li><strong>개발 단계용</strong></li>
<li>코드를 실행 중에 바로 컴파일, Hot Reload 가능, 초기 실행은 느리지만 반복 개발 속도가 빠름</li>
</ul>
<h4 id="aot-ahead-of-time">AOT (Ahead-Of-Time)</h4>
<ul>
<li><strong>배포용</strong></li>
<li>실행 전에 네이티브 코드로 미리 컴파일, 앱 시작 속도 빠르고 실행 성능 높음, 최종 빌드 시 용량은 커질 수 있음</li>
</ul>
<hr>
<h3 id="dart-기본-문법">Dart 기본 문법</h3>
<ul>
<li><p><strong>변수 선언</strong></p>
<ul>
<li><code>var</code>: 타입 추론 후 고정</li>
<li><code>dynamic</code>: 어떤 타입도 허용, 런타임에 타입 결정</li>
<li><code>final</code>: 런타임에 값 확정</li>
<li><code>const</code>: 컴파일 타임 상수, 불변 + 컴파일 최적화</li>
</ul>
</li>
<li><p><strong>기본 타입</strong></p>
<ul>
<li><code>String</code>, <code>int</code>, <code>double</code>, <code>bool</code></li>
</ul>
</li>
<li><p><strong>컬렉션 타입</strong></p>
<ul>
<li><code>List</code>: 순서 기반, 인덱스로 접근</li>
<li><code>Map</code>: key–value 구조</li>
<li><code>Set</code>: 중복 없음</li>
<li><code>enum</code>: 값 집합 정이</li>
</ul>
</li>
<li><p><strong>제어문</strong></p>
<ul>
<li><code>if / else</code>, <code>switch</code></li>
<li><code>for</code>, <code>for-in</code>, <code>while</code>, <code>do-while</code></li>
</ul>
</li>
<li><p><strong>함수 문법</strong></p>
<ul>
<li>반환값 + 매개변수</li>
<li>익명 함수 &amp; 람다 <code>(args) =&gt; expr</code></li>
</ul>
</li>
<li><p><strong>typedef</strong></p>
<ul>
<li>함수 타입에 별칭 부여</li>
</ul>
</li>
<li><p><strong>try / catch / finally</strong></p>
<ul>
<li><code>try</code>: 실행</li>
<li><code>catch(e)</code>: 예외 처리</li>
<li><code>on SomeException</code>: 특정 예외 처리</li>
<li><code>finally</code>: 무조건 실행</li>
</ul>
</li>
<li><p><strong>비동기 문법</strong></p>
<ul>
<li><code>async</code>, <code>await</code> 사용</li>
<li><code>Future</code>: 1회성 비동기 결과</li>
<li><code>Stream</code>: 지속적으로 여러 값을 방출</li>
<li><img src="https://velog.velcdn.com/images/squee_z_e/post/ee499bd5-1d40-4b82-8d17-242757cb0a40/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h3 id="객체지향-개념-정리">객체지향 개념 정리</h3>
<h4 id="클래스-class">클래스 (Class)</h4>
<ul>
<li>데이터(필드)와 동작(메서드)을 하나로 묶는 기본 단위</li>
<li>실제 앱 구조는 거의 전부 “클래스 기반”으로 설계됨</li>
<li>Flutter 위젯도 모두 클래스 → Widget 이해 = Class 이해</li>
</ul>
<h4 id="인스턴스-instance--멤버-member">인스턴스 (Instance) &amp; 멤버 (Member)</h4>
<ul>
<li>클래스가 설계도라면 인스턴스는 그 설계도로 만든 <strong>실제 객체</strong> (붕어빵틀과 붕어빵)</li>
<li>인스턴스화는 클래스를 실제 객체로 생성하는 과정</li>
<li>생성된 인스턴스는 각각 독립된 상태를 가짐. 인스턴스 멤버(필드/메서드)가 객체마다 독립적으로 존재</li>
<li>클래스를 인스턴스화 하면 클래스의 인스턴스를 변수로 저장 가능</li>
</ul>
<h4 id="생성자-constructor">생성자 (Constructor)</h4>
<ul>
<li>객체 생성 시 초기값을 정의</li>
<li>기본 생성자 + named constructor 모두 지원</li>
<li>생성자 오버로딩 대신 named constructor 방식 사용</li>
</ul>
<h4 id="게터-getter--세터-setter">게터 (Getter) / 세터 (Setter)</h4>
<ul>
<li>게터는 객체의 내부 값을 읽기 위한 접근자</li>
<li>세터는 객체의 내부 값을 변경하기 위한 설정자</li>
<li>최근 변수의 값을 불변성(인스턴스화 후 변경할 수 없는) 특성으로 사용해서 세터는 잘 안씀</li>
</ul>
<h4 id="상속-extends">상속 (extends)</h4>
<ul>
<li>부모 클래스의 기능을 그대로 물려받음</li>
<li>하나의 클래스만 상속 가능(단일 상속)</li>
<li>재사용성과 일관성을 유지할 때 유용</li>
<li>Flutter에서 공통 UI/로직 구조를 만들 때 자주 등장</li>
</ul>
<h4 id="인터페이스-implements">인터페이스 (implements)</h4>
<ul>
<li>클래스가 반드시 가져야 할 기능 “목록”</li>
<li>Dart에서는 모든 클래스가 자동으로 인터페이스 역할 가능</li>
<li><strong>implements는 반드시 모든 메서드를 재정의해야 함</strong></li>
<li>명확한 규약/프로토콜을 만들 때 효과적</li>
</ul>
<h4 id="추상-클래스-abstract">추상 클래스 (abstract)</h4>
<ul>
<li>인스턴스를 만들 수 없음</li>
<li>공통 동작의 뼈대를 정의하는 용도</li>
<li>“기능의 템플릿” 역할</li>
<li>UI · 데이터 모델 계층에 자주 사용됨</li>
</ul>
<h4 id="오버라이드-override">오버라이드 (override)</h4>
<ul>
<li>상속받은 메서드/필드를 자식 클래스에서 재정의</li>
<li>클래스 간 동작을 상황에 맞게 다르게 표현</li>
<li><code>@override</code> 애노테이션을 사용해 의도를 명확히 표시함</li>
</ul>
<h4 id="믹스인-mixin">믹스인 (Mixin)</h4>
<ul>
<li>상속과 달리 “기능만” 주입하는 방식</li>
<li>여러 클래스에 공통 기능을 공유하고 싶을 때 사용</li>
<li>다중 상속이 없는 Dart에서 <strong>재사용성을 강화하는 핵심 기능</strong></li>
</ul>
<h4 id="제네릭-generics">제네릭 (Generics)</h4>
<ul>
<li>타입을 외부에서 주입해 재사용성과 안정성을 높임</li>
<li>클래스/함수/컬렉션 모두 제네릭을 사용 가능</li>
<li>&lt;&gt; 안에 타입을 명시 <code>List&lt;int&gt;</code>, <code>Map&lt;String, dynamic&gt;</code></li>
</ul>
<h4 id="스태틱-static">스태틱 (Static)</h4>
<ul>
<li>클래스에 직접 귀속되는 값/메서드</li>
<li>인스턴스마다 복사되지 않고 모두가 공유</li>
<li>상태 보관용으로 남용은 위험 → 설정/상수에 적합</li>
</ul>
<h4 id="캐스케이드-연산자-cascade-operator">캐스케이드 연산자 (Cascade Operator)</h4>
<ul>
<li>하나의 인스턴스에 대해 연속적으로 필드·메서드를 호출하는 문법</li>
<li>인스턴스 초기화나 설정 코드를 간결하게 작성할 때 유용</li>
</ul>
<hr>
<h3 id="플러터-구조와-통신">플러터 구조와 통신</h3>
<div style="display:flex; gap:8px;">
  <img src="https://velog.velcdn.com/images/squee_z_e/post/a83513cb-279b-4ffa-98d4-a246aad51d96/image.png" style="width:50%;" />
  <img src="https://velog.velcdn.com/images/squee_z_e/post/39401cca-7736-424f-9bdf-a998c12f2f6c/image.png" style="width:50%;" />
</div>

<h4 id="플러터-구조framework-→-engine-→-embedder">플러터 구조(Framework → Engine → Embedder)</h4>
<ul>
<li><strong>Framework(Dart)</strong>: Material/Cupertino, Widgets, Rendering 등 UI 계층. 개발자가 직접 사용하는 부분</li>
<li><strong>Engine(C/C++)</strong>: 렌더링, 텍스트 레이아웃, Dart VM, 이벤트 처리 등 핵심 런타임</li>
<li><strong>Embedder(Platform-specific)</strong>: iOS/Android/웹 등 플랫폼에 맞게 화면, 스레드, 플러그인, 이벤트 루프를 설정</li>
</ul>
<blockquote>
<p>개발자가 Dart로 작성한 UI 코드는 Engine에서 처리되어 플랫폼 Embedder 위에서 실행되는 구조</p>
</blockquote>
<h4 id="플러터-vs-리액트-네이티브-플랫폼-통신-구조">플러터 vs 리액트 네이티브 플랫폼 통신 구조</h4>
<ul>
<li><strong>Flutter</strong>: Dart 코드가 직접 Engine을 통해 <strong>위젯을 렌더링</strong>
플랫폼 기능은 <strong>메서드 채널</strong>로 호출해 Native 서비스와 통신
→ 브릿지 비용이 적고 렌더링을 엔진이 직접 수행하므로 성능 이점이 있음</li>
<li><strong>React Native</strong>: JavaScript ↔ Bridge ↔ Native 위젯 구조
→ 브릿지를 거쳐 OEM UI를 렌더링하므로 통신 비용이 크고 Flutter보다 오버헤드가 큼</li>
</ul>
<blockquote>
<p><strong>Flutter는 브릿지 없이 자체 엔진으로 UI를 그림</strong>, RN은 <strong>네이티브 UI를 호출하는 중간 브릿지가 필요</strong></p>
</blockquote>
<hr>
<h3 id="플러터에서의-위젯-widget">플러터에서의 위젯 (Widget)</h3>
<h4 id="모든-것이-위젯이다"><strong>모든 것이 위젯이다</strong></h4>
<div style="display:flex; gap:8px;">
  <img src="https://velog.velcdn.com/images/squee_z_e/post/6f881365-51f7-47ad-9ae0-2067674d8318/image.png" style="width:40%;" />
  <img src="https://velog.velcdn.com/images/squee_z_e/post/6727c8cc-3f1e-4829-a98a-51847f8d1f9a/image.png" style="width:25%;" />
  <img src="https://velog.velcdn.com/images/squee_z_e/post/db38c515-d6ff-4cd9-ba7d-d34d56783ea2/image.png" style="width:30%;" />
</div>

<h4 id="위젯-widget">위젯 (Widget)</h4>
<ul>
<li>플러터에서 화면을 구성하는 모든 요소의 최소 단위</li>
<li>텍스트, 버튼, 아이콘 같은 눈에 보이는 UI뿐만 아니라
레이아웃, 패딩, 정렬, 애니메이션 같은 보이지 않는 요소도 모두 위젯</li>
<li>플러터는 위젯 트리(widget tree) 구조로 화면을 구성
→ 부모 위젯 안에 자식 위젯이 포함되며, 전체 화면이 트리처럼 표현</li>
</ul>
<h4 id="child와-children의-차이">child와 children의 차이</h4>
<ul>
<li><table>
<thead>
<tr>
<th>구분</th>
<th>child</th>
<th>children</th>
</tr>
</thead>
<tbody><tr>
<td>수용 가능한 자식 수</td>
<td>1개</td>
<td>여러 개</td>
</tr>
<tr>
<td>타입</td>
<td>Widget</td>
<td>List<Widget></td>
</tr>
<tr>
<td>사용 목적</td>
<td>꾸미기, 감싸기, 위치 조정</td>
<td>레이아웃 배치, 리스트 구조</td>
</tr>
<tr>
<td>사용 가능 예</td>
<td>Container, SizedBox</td>
<td>Row, Column, ListView</td>
</tr>
</tbody></table>
</li>
</ul>
<hr>
<h3 id="콜백-함수-웹뷰-위젯-statelesswidget-vs-statefulwidget-setstate">콜백 함수, 웹뷰 위젯, StatelessWidget vs StatefulWidget, setState</h3>
<h4 id="콜백-함수">콜백 함수</h4>
<ul>
<li>특정 시점에 실행되도록 <strong>함수를 인자로 전달하는 구조</strong></li>
<li>버튼 이벤트, 제스처, WebView 이벤트 등에서 사용</li>
<li>UI가 이벤트 기반으로 동작하기 때문에 필수 개념</li>
</ul>
<h4 id="webview-위젯">WebView 위젯</h4>
<ul>
<li>앱 내부에서 <strong>웹 페이지를 렌더링</strong>하는 위젯</li>
<li>결제 화면, 약관, 외부 링크 구현에 사용</li>
<li>URL 로딩, 뒤로가기, JavaScript 통신 지원</li>
</ul>
<h4 id="statelesswidget">StatelessWidget</h4>
<ul>
<li><strong>내부 상태가 없는 위젯</strong></li>
<li>부모가 전달한 값(생성자 매개변수)이 변경될 때만 build가 다시 실행됨</li>
<li>단순 UI 요소에 적합</li>
</ul>
<h4 id="statefulwidget">StatefulWidget</h4>
<ul>
<li><strong>상태(State)를 가지고 UI를 갱신하는 위젯</strong></li>
<li>실제 상태는 State 객체에 저장됨</li>
<li>상태 변화에 따라 build가 재실행됨</li>
<li>입력값 변화, 애니메이션, 화면 갱신에 사용</li>
<li>(사진 참고) 상태 변경이 없는 생명 주기 / StatefulWidget 생성자의 매개변수가 변경됐을 때의 생명 주기 / State 자체적으로 build()를 재실행할 때 생명주기</li>
</ul>
<div style="display:flex; gap:8px;">
  <img src="https://velog.velcdn.com/images/squee_z_e/post/420d37be-49ce-4bd2-9214-7052307b27ed/image.png" style="width:30%;" />
  <img src="https://velog.velcdn.com/images/squee_z_e/post/22e01d9e-3492-4e95-b495-cb46a0353f75/image.png" style="width:30%;" />
  <img src="https://velog.velcdn.com/images/squee_z_e/post/7ff09d2b-e15e-414c-ad7d-d7e51c91f8aa/image.png" style="width:30%;" />
</div>


<h4 id="setstate-함수">setState 함수</h4>
<ul>
<li>StatefulWidget에서 <strong>UI를 다시 그릴 때 사용하는 함수</strong></li>
<li>setState 내부에서 상태 값을 변경하면 해당 위젯만 다시 build됨</li>
<li>전체 앱이 다시 그려지는 것이 아니라 필요한 위젯 트리만 갱신됨</li>
</ul>
<hr>
<h3 id="디자인-패턴-비교-mvc--mvp--mvvm--mvi">디자인 패턴 비교 (MVC / MVP / MVVM / MVI)</h3>
<p>  <img src="https://velog.velcdn.com/images/squee_z_e/post/c345a496-9a38-4bb0-99e0-79fc55f09812/image.png" alt=""></p>
<h4 id="아키텍처-패턴-비교표">아키텍처 패턴 비교표</h4>
<table>
<thead>
<tr>
<th>패턴</th>
<th>구조</th>
<th>데이터 흐름</th>
<th>View와 로직의 분리</th>
<th>장점</th>
<th>단점</th>
<th>Flutter와의 궁합</th>
</tr>
</thead>
<tbody><tr>
<td><strong>MVC</strong></td>
<td>Model – View – Controller</td>
<td>양방향</td>
<td>View와 Controller 결합도가 높음</td>
<td>단순, 이해 쉬움</td>
<td>앱 규모 커지면 Controller 비대</td>
<td>보통 안 맞음 (위젯 트리와 안 맞음)</td>
</tr>
<tr>
<td><strong>MVP</strong></td>
<td>Model – View – Presenter</td>
<td>양방향</td>
<td>Presenter가 View를 직접 업데이트</td>
<td>테스트 용이</td>
<td>View–Presenter 의존성 여전</td>
<td>Flutter와는 잘 안 맞음</td>
</tr>
<tr>
<td><strong>MVVM</strong></td>
<td>Model – View – ViewModel</td>
<td>단방향(or 단방향 데이터 바인딩)</td>
<td>ViewModel이 상태 제공, View는 구독</td>
<td>UI–로직 분리 명확, 유지보수 쉬움</td>
<td>바인딩 구조 이해 필요</td>
<td><strong>Flutter와 가장 잘 맞음</strong></td>
</tr>
<tr>
<td><strong>MVI</strong></td>
<td>Model – View – Intent</td>
<td>단방향 (Action → State)</td>
<td>완전한 단방향(state machine)</td>
<td>예측 가능, 상태 추적 쉬움</td>
<td>코드 양 많고 복잡</td>
<td><strong>Bloc, Redux 계열이 여기에 가까움</strong></td>
</tr>
</tbody></table>
<h3 id="왜-이런-디자인-패턴들이-중요한가">왜 이런 디자인 패턴들이 중요한가?</h3>
<h4 id="1-화면이-커질수록-코드가-복잡해지기-때문">1) 화면이 커질수록 코드가 복잡해지기 때문</h4>
<ul>
<li>버튼 하나 눌러도 다양한 로직이 필요한데, 이를 한 파일에서 처리하면 금방 “스파게티 코드”가 됨<ul>
<li>UI 업데이트 / 데이터 갱신 / API 요청 / 라우팅 처리</li>
</ul>
</li>
</ul>
<h4 id="2-ui와-비즈니스-로직을-분리하면-유지보수성이-올라감">2) UI와 비즈니스 로직을 분리하면 유지보수성이 올라감</h4>
<ul>
<li>UI 변경 시 로직 영향 없음</li>
<li>로직 변경 시 UI 영향 없음</li>
<li>테스트가 쉬워짐</li>
</ul>
<h4 id="3-플러터는-선언형-ui이기-때문에-상태state-설계가-핵심">3) 플러터는 선언형 UI이기 때문에 “상태(State)” 설계가 핵심</h4>
<ul>
<li>UI를 직접 수정하는 것이 아니라 <strong>상태를 바꾸면 UI가 자동 업데이트되는 구조</strong>
→ 즉, “상태 중심 아키텍처&quot;가 중요</li>
<li>그래서 MVC처럼 View와 Controller가 얽힌 구조는 잘 안 맞음
→ MVVM 또는 MVI 계열이 적합</li>
</ul>
<h3 id="플러터는-어떤-아키텍처-패턴과-잘-맞는가---mvvm-또는-mvi-계열">플러터는 어떤 아키텍처 패턴과 잘 맞는가? -&gt; MVVM 또는 MVI 계열</h3>
<ul>
<li>선언형 UI 방식(Build를 계속 다시 그리는 구조)</li>
<li>상태 중심(State-driven) 아키텍처</li>
<li>Provider, Riverpod, Bloc, Cubit 등 생태계 도구들이 모두 MVVM/MVI 구조를 지원</li>
</ul>
<blockquote>
<p>“MVC나 MVP처럼 View가 직접 로직을 호출하는 구조”보다
“ViewModel/Bloc 같은 상태 제공자를 구독해 UI를 만든다” 자연스러움</p>
</blockquote>
<ul>
<li>참고 : <a href="https://dev.to/arslanyousaf12/flutter-architecture-patterns-a-developers-journey-through-mvc-mvp-mvvm-and-mvi-bej">플러터 아키텍쳐 패턴</a> - 플러터가 MVVM과 잘 맞는이유</li>
</ul>
<h3 id="플러터에서-자주-사용하는-상태관리-도구들">플러터에서 자주 사용하는 상태관리 도구들</h3>
<table>
<thead>
<tr>
<th>상태관리 도구</th>
<th>철학</th>
<th>어떤 패턴에 가까움</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td><strong>setState</strong></td>
<td>직접 상태 변경</td>
<td>패턴 없음</td>
<td>소규모 화면에 적합</td>
</tr>
<tr>
<td><strong>Provider</strong></td>
<td>View ↔ ViewModel</td>
<td>MVVM</td>
<td>가장 기본적인 MVVM 구현 도구</td>
</tr>
<tr>
<td><strong>Riverpod</strong></td>
<td>상태 + DI 컨테이너</td>
<td>MVVM</td>
<td>Provider의 발전형, 강력함</td>
</tr>
<tr>
<td><strong>Bloc / Cubit</strong></td>
<td>Event → State</td>
<td>MVI</td>
<td>기업·대규모 프로젝트에서 선호</td>
</tr>
<tr>
<td><strong>Redux</strong></td>
<td>글로벌 상태 관리</td>
<td>MVI</td>
<td>단방향 구조, 규모 크면 좋음</td>
</tr>
<tr>
<td><strong>GetX</strong></td>
<td>반응형</td>
<td>MVVM</td>
<td>빠르지만 안정성 논란</td>
</tr>
</tbody></table>
<h3 id="만약-팀에서-bloc을-사용한다면">만약 팀에서 Bloc을 사용한다면?</h3>
<blockquote>
<p><strong>MVI(Model–View–Intent) 기반 단방향 상태관리 아키텍처를 사용한다는 의미</strong>
  View → Event → Bloc → 새로운 State → View rebuild</p>
</blockquote>
<ul>
<li><p>전통적인 MVVM은 ViewModel이 상태를 가지고 있고, View는 상태만 읽음(양방향 바인딩도 가능)</p>
</li>
<li><p>Bloç을 쓴다면(MVI) <strong>MVVM보다 더 엄격한 단방향 흐름</strong>을 가짐
→ 코드가 늘어나는 대신, <strong>예측 가능성 + 테스트 용이성 + 유지보수성</strong>이 뛰어나 회사들이 선호함</p>
<ul>
<li>View는 Event만 Bloc에게 전달</li>
<li>Bloc은 Event + 현재 State로 새로운 State를 생성</li>
<li>View는 이 State를 구독</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/12 오늘은 적을 게 없어서 알고리즘 공부]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251112</link>
            <guid>https://velog.io/@squee_z_e/TIL-251112</guid>
            <pubDate>Wed, 12 Nov 2025 09:39:25 GMT</pubDate>
            <description><![CDATA[<p>오늘은 지난 개발 회고하고.. 발표하고..
생성형 AI로 프롬포트 작성해서 음악을 생성해봤다.
개발을 한 게 없어서 적을 게 없지만
지금은 할 일을 다 마친 상태로 야근 중이기 때문에 뭐라도 끄적거려본다
어차피 집가면 공부하고 다시 적어야 할 듯</p>
<hr>
<h3 id="백준-1644-소수의-연속합---python">백준 1644. 소수의 연속합 - python</h3>
<p><a href="https://www.acmicpc.net/problem/1644">문제 링크</a> </p>
<h4 id="문제-설명">문제 설명</h4>
<p>하나 이상의 연속된 소수의 합으로 나타낼 수 있는 자연수들이 있다. 몇 가지 자연수의 예를 들어 보면 다음과 같다.</p>

<ul>
    <li>3 : 3 (한 가지)</li>
    <li>41 : 2+3+5+7+11+13 = 11+13+17 = 41 (세 가지)</li>
    <li>53 : 5+7+11+13+17 = 53 (두 가지)</li>
</ul>

<p>하지만 연속된 소수의 합으로 나타낼 수 없는 자연수들도 있는데, 20이 그 예이다. 7+13을 계산하면 20이 되기는 하나 7과 13이 연속이 아니기에 적합한 표현이 아니다. 또한 한 소수는 반드시 한 번만 덧셈에 사용될 수 있기 때문에, 3+5+5+7과 같은 표현도 적합하지 않다.</p>

<p>자연수가 주어졌을 때, 이 자연수를 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 구하는 프로그램을 작성하시오.</p>

<h4 id="입력">입력</h4>
 <p>첫째 줄에 자연수 N이 주어진다. (1 ≤ N ≤ 4,000,000)</p>

<h4 id="출력">출력</h4>
 <p>첫째 줄에 자연수 N을 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 출력한다.</p>

<h4 id="내-풀이">내 풀이</h4>
<p>뭔가 굉장히 별로인 방법인걸 알지만 슬라이딩 윈도우 구현 방식이 기억이 안나 꾸역꾸역 풀었다.
당연히 시공간 자원 많이 씀..
범위 설정을 자꾸 틀려서 연습을 많이 해야할 것 같다.
에라토스 뭐시기는 소수 구할 때 써야한다는 건 기억나는데 방법이 기억 안나서 나무위키 잠깐 봤음</p>
<pre><code class="language-python"># 백준 1644번 소수의 연속합

N = int(input())
answer = 0

# 예외 처리: 2 미만이면 만들 수 있는 연속 소수 합 없음
if N &lt; 2:
    print(0)
    exit()

# 에라토스테네스의 체 (i*i부터 지우기)
temp_list = [i for i in range(N + 1)]
temp_list[0] = 0
temp_list[1] = 0

limit = int(N ** 0.5)
for i in range(2, limit + 1):
    if temp_list[i] != 0:
        start = i * i
        step = i
        for m in range(start, N + 1, step):
            temp_list[m] = 0

prime_list = [v for v in temp_list if v &gt; 0]

# 두 포인터로 연속합 세기 (오른쪽 늘리고, N 이상이면 왼쪽 줄이기)
left = 0
right = 0
temp_sum = 0

while True:
    if temp_sum &gt;= N:
        if temp_sum == N:
            answer += 1
        temp_sum -= prime_list[left]
        left += 1
    else:
        if right == len(prime_list):
            break
        temp_sum += prime_list[right]
        right += 1

print(answer)</code></pre>
<p>아래는 gpt와 함께 코드를 효율적으로 다듬은 거다.</p>
<pre><code class="language-python"># 백준 1644번 소수의 연속합

N = int(input())
answer = 0

# 예외 처리: 2 미만이면 만들 수 있는 연속 소수 합 없음
if N &lt; 2:
    print(0)
    exit()

# 에라토스테네스의 체 (i*i부터 지우기)
temp_list = [i for i in range(N + 1)]
temp_list[0] = 0
temp_list[1] = 0

limit = int(N ** 0.5)
for i in range(2, limit + 1):
    if temp_list[i] != 0:
        start = i * i
        step = i
        for m in range(start, N + 1, step):
            temp_list[m] = 0

prime_list = [v for v in temp_list if v &gt; 0]

# 두 포인터로 연속합 세기 (오른쪽 늘리고, N 이상이면 왼쪽 줄이기)

left = 0
right = 0
temp_sum = 0

while True:
    if temp_sum &gt;= N:
        if temp_sum == N:
            answer += 1
        temp_sum -= prime_list[left]
        left += 1
    else:
        if right == len(prime_list):
            break
        temp_sum += prime_list[right]
        right += 1

print(answer)</code></pre>
<p>사실 오늘 스터디에서 두 문제를 풀어야 했지만 한 문제는 포기</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/11 공식문서를 제발 잘봅시다]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251111</link>
            <guid>https://velog.io/@squee_z_e/TIL-251111</guid>
            <pubDate>Tue, 11 Nov 2025 09:53:21 GMT</pubDate>
            <description><![CDATA[<h3 id="supabase-edge-function--cron-사용하기">supabase edge function + cron 사용하기</h3>
<p><a href="https://supabase.com/docs/guides/functions/schedule-functions">https://supabase.com/docs/guides/functions/schedule-functions</a>
<a href="https://supabase.com/docs/guides/database/vault">https://supabase.com/docs/guides/database/vault</a></p>
<p>수퍼베이스에서 기본 제공하는 스케줄러를 사용해서 예약 30분 전 슬랙 알림을 보내는 기능을 구현했다.</p>
<p>service_role_key를 헤더에 하드코딩하는 미친짓을 해보기도 하고
anon, publishable 키들을 사용해 보기도 하고
cron secret이라는 임시키를 만들어서 헤더에도 넣어보고 바디에도 넣어보았지만
401 에러만 뱉고 로그를 찍지도 않는 상황이 반복됐다.</p>
<ul>
<li>vault로 DB에 비밀 키 저장해봐라. 
edge function에는 env에 키 넣어두고 비교하게 하면 헤더에 하드코딩할 필요가 없다.</li>
<li>대부분 서비스가 JWT 토큰 검증할 텐데 이것도 그런 거 아니냐?</li>
</ul>
<p>등의 조언을 들으면서 로그를 찍어보다가,
일단 edge function이 JWT 토큰 형식으로 Authorization 헤더 검사를 하는 것 같다는 결론에 도달했다.
공식 문서에서도 anon 키를 쓰라고 되어 있어서,
쓸데없이 하던 거 다 갖다 버리고 공식 문서대로 하되 anon 키를 vault에 저장해두고 불러 쓰는 식으로 바꾸니까 됐다.</p>
<pre><code class="language-sql">-- **실행한 sql문 정리**

-- vault에 anon key 넣기

SELECT vault.create_secret(
  &#39;eyJ...&#39;,  -- Dashboard의 실제 anon key (JWT 형식)
  &#39;publishable_key&#39;
);

-- pg_cron job 업데이트 (Authorization 헤더 포함)

SELECT cron.alter_job(
  (SELECT jobid FROM cron.job WHERE command LIKE &#39;%check-reminders%&#39; LIMIT 1),
  command := $$
  SELECT
    net.http_post(
      url := &#39;https://프로젝트.supabase.co/functions/v1/check-reminders&#39;,
      headers := jsonb_build_object(
        &#39;Content-Type&#39;, &#39;application/json&#39;,
        &#39;Authorization&#39;, &#39;Bearer &#39; || (
          SELECT decrypted_secret 
          FROM vault.decrypted_secrets 
          WHERE name = &#39;publishable_key&#39;
        )
      ),
      body := &#39;{}&#39;::jsonb
    ) AS request_id;
  $$
);


-- 업데이트된 job 확인 (Authorization과 Bearer 포함 여부 확인용)
SELECT command 
FROM cron.job 
WHERE command LIKE &#39;%check-reminders%&#39;;


-- 수동으로 함수 호출 테스트

SELECT
  net.http_post(
    url := &#39;https://프로젝트.supabase.co/functions/v1/check-reminders&#39;,
    headers := jsonb_build_object(
      &#39;Content-Type&#39;, &#39;application/json&#39;,
      &#39;Authorization&#39;, &#39;Bearer &#39; || (
        SELECT decrypted_secret 
        FROM vault.decrypted_secrets 
        WHERE name = &#39;publishable_key&#39;
      )
    ),
    body := &#39;{}&#39;::jsonb
  ) AS request_id;</code></pre>
<p>해결된 이유는... supabase key에 대한 <strong>깃허브 토론</strong>과 <strong>공식 문서</strong>에서 실마리를 찾을 수 있었다.
이런거 많이 봐야지 개발실력이 는다는 유튜브 영상을 봤는데, 
트러블 슈팅할 때는 무조건 이것들부터 보는 습관을 길러야 할 것 같다.. 
API key가 있는 페이지만 봐서는 절대 이해 불가</p>
<p><a href="https://supabase.com/docs/guides/api/api-keys">https://supabase.com/docs/guides/api/api-keys</a>
<a href="https://github.com/orgs/supabase/discussions/29260">https://github.com/orgs/supabase/discussions/29260</a></p>
<p>최근 Supabase는 보안 강화를 위해 <code>anon / service_role</code> 키 대신
<code>publishable / secret</code> 키 체계를 새로 도입했는데,
이 새 키들(욕 아님)은 JWT 형식이 아니다.
그래서 Authorization 헤더에 <code>Bearer sb_publishable_...</code> 형태로 넣으면
Edge Function이 “JWT가 아님”이라며 무조건 401을 반환한다.</p>
<p>반면에 기존의 anon 키는 여전히 JWT 기반이어서
기본적으로 JWT 검증이 걸려 있는 Edge Function에서도 정상적으로 인증이 통과된다.
즉, publishable 키로는 안 되고 anon 키로는 되는 이유가 바로 이 구조 차이 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/1967a5b0-0f58-4e4d-9c85-e62eb2f9e267/image.png" alt=""></p>
<p>edge function에 자동으로 들어가있는 anon key는 로그 찍어보면 publishable형식이다. 
그래서 DB에서 edge function 호출할 때 publishable 키를 써야 할 줄 알았지만?
요청 헤더에 JWT(= 레거시 anon 키)를 보내줘야 통과되는 구조라는 거임... 껄껄</p>
<p>기본 설정 그대로라면 Authorization 헤더 + JWT가 있어야 하는데, 
헤더 검증을 안하게 하려면 배포 시 --no-verify-jwt 옵션을 사용했거나, 
함수 설정에서JWT 검증 해 상태여야 한다고 한다. 
cli 가 익숙하지 않아서 edge funtion을 수동으로 배포한 사람은...
배포한 함수 페이지 -&gt; details -&gt; funciton configuration에서 끄면 되는 것 같다.
사이트가 많이 개편되어서인지 제대로 경로를 적어놓은 곳이 별로 없다.</p>
<p>AI는 anon key만 쓰거나 jwt 인증을 아예 꺼버리면 보안 쪽 문제가 있으니
cron_secret 같은 임시 키를 만들어서 요청 헤더나 body에 같이 넣고 Edge Function에서 직접 비교해서
외부 무단 접근 막으라는데 일단은 생략..</p>
<blockquote>
<p>추후 변경
결국 jwt 검증 아예 끄고 랜덤값 생성한거 DB엔 valut로, edge funtion엔 환경변수로 넣음
헤더에서의 검증에서는 이 값을 사용하는걸로 바꿨음</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TIL) 11/10 개념부터 잡고 코드 작업]]></title>
            <link>https://velog.io/@squee_z_e/TIL-251110</link>
            <guid>https://velog.io/@squee_z_e/TIL-251110</guid>
            <pubDate>Mon, 10 Nov 2025 01:26:12 GMT</pubDate>
            <description><![CDATA[<h3 id="supabase-사용-시-잡고-가야-할-개념">supabase 사용 시 잡고 가야 할 개념</h3>
<h4 id="1-db-함수--프로시저">1. <strong>DB 함수 / 프로시저</strong></h4>
<ul>
<li>Supabase(PostgreSQL)에서는 <strong>DB 함수가 곧 프로시저</strong>처럼 쓰임
데이터를 조회하거나 수정하는 로직을 DB 내부에 저장해두고 실행하는 코드 블록</li>
<li>실행 위치: 데이터베이스 내부</li>
<li>역할: 여러 SQL을 하나로 묶어 재사용·트랜잭션 처리에 사용</li>
<li>장점: 빠르고 일관성 있음</li>
<li>단점: DB 종속이고, 형상관리 어려움</li>
</ul>
<h4 id="2-트리거-trigger">2. <strong>트리거 (Trigger)</strong></h4>
<ul>
<li>특정 테이블에 <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>가 일어날 때
<strong>자동으로 DB 함수(프로시저)를 실행</strong>시키는 메커니즘</li>
<li>실행 위치: 데이터베이스 내부 (함수 호출자 역할</li>
<li>역할: 데이터 변경 감지 → 후속 처리 (예: 로그 기록, 알림 호출)</li>
<li>트리거 자체는 로직이 아니라 “언제 함수를 실행할지 정의한 연결 장치”</li>
</ul>
<h4 id="3-rpc-remote-procedure-call">3. RPC (Remote Procedure Call)</h4>
<ul>
<li>정의: 클라이언트가 Supabase에 정의된 DB 함수를 원격으로 호출</li>
<li>실행 위치: Supabase API 게이트웨이를 통해 전달되어 <strong>DB 내부 함수가 실행</strong></li>
<li>역할: REST API 없이도 데이터베이스 내부 로직(DB 함수)을 직접 호출할 수 있게 함</li>
<li>특징:<ul>
<li><code>supabase.rpc(&#39;function_name&#39;)</code> 형태로 호출</li>
<li>네트워크 요청처럼 보이지만, 실제 연산은 DB 내부에서 수행되어 <strong>지연이 적음</strong></li>
<li>DB 함수가 API처럼 동작하므로 <strong>백엔드 서버 없이도 비즈니스 로직 노출 가능</strong></li>
</ul>
</li>
</ul>
<h4 id="4-rls-row-level-security-와-policy">4. RLS (Row Level Security) 와 Policy</h4>
<ul>
<li>RLS는 데이터베이스의 행 단위 접근 제어 기능,
Policy는 그 RLS 위에서 구체적인 접근 조건을 정의하는 규칙</li>
<li>로그인된 사용자 정보(auth.uid())를 기준으로,
각 사용자가 자신에게 허용된 행만 조회·수정할 수 있도록 제한</li>
<li>실행 위치: DB 내부 (쿼리 실행 시점)</li>
<li>역할: “누가 어떤 조건에서 어떤 데이터에 접근 가능한가”를 정의하는 보안 레이어</li>
</ul>
<h4 id="5-edge-function">5. <strong>Edge Function</strong></h4>
<ul>
<li>DB 밖에서 동작하는 <strong>서버리스 함수</strong></li>
<li>실행 위치: <strong>Supabase의 Edge 네트워크 (Deno 기반 서버)</strong></li>
<li>역할: 외부 API 호출, Slack 알림, 비즈니스 로직 등
DB에 넣기 애매한 서버 역할을 담당</li>
<li>장점: 서버 따로 안 두고 로직 추가 가능</li>
<li>단점: 형상관리·테스트 제약이 있어서 팀 규모 커지면 일반 서버 코드로 대체하는 경우 많음</li>
</ul>
<blockquote>
<p><strong>DB 함수(=프로시저)</strong> 는 로직의 본체,
<strong>트리거</strong>는 그걸 자동으로 실행하는 장치,
<strong>RPC</strong>는 DB 함수를 외부에서 API처럼 호출하는 통로,
<strong>RLS</strong>는 접근을 제한하는 보안 규칙,
<strong>Edge Function</strong>은 DB 밖에서 돌아가는 확장 로직이다.</p>
</blockquote>
<hr>
<h3 id="supabase에-slack-bot-연동하기">supabase에 slack bot 연동하기</h3>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/237e3146-bfcd-4018-91a8-2dfd6baea206/image.png" alt="">
<a href="https://api.slack.com/apps/">https://api.slack.com/apps/</a>
create new app -&gt; from a manifest -&gt; select a workspace 해서 새 app 생성
app home -&gt; Your App’s Presence in Slack에서 이름 바꿀 수 있음
OAuth  &amp; Permissions -&gt; scope에서 chat:write 권한 추가, OAuth Tokens에서 install to 워크스페이스
이러면 Bot User OAuth Token 보일거임</p>
<p>supabase에서 bot token 사용해서 알림 보내는 트리거 함수를 edge function에 등록
settings -&gt; edge functions -&gt; secres에서 bot token 등록
(로컬에서 테스트할 땐 환경변수에 저장) </p>
<blockquote>
<p>후술하지만 다른 방식도 있습니다</p>
</blockquote>
<p>slack oicd 연동해뒀으면 Authentication에 Sign In / Providers 쪽에 slack oicd가 enabled되어 있을텐데... 봇을 쓴다고 해서 client id랑 client secret을 전부 바꿀 필요는 없는듯? 워크스페이스가 다를 때만 의미가 있는 것 같음</p>
<hr>
<h3 id="supabase에서-스케줄러-사용하기">supabase에서 스케줄러 사용하기</h3>
<h4 id="문제-상황">문제 상황</h4>
<ul>
<li>30분 전 알림은 주기적 실행이 필요</li>
<li>Docker 사내망 배포 환경</li>
<li>별도 스케줄러 인프라 구축 부담</li>
</ul>
<h4 id="해결-방법-비교">해결 방법 비교</h4>
<table>
<thead>
<tr>
<th>방식</th>
<th>장점</th>
<th>단점</th>
<th>선택 여부</th>
</tr>
</thead>
<tbody><tr>
<td>Next.js API + 외부 cron</td>
<td>코드베이스 통합</td>
<td>별도 서버/스케줄러 필요</td>
<td>❌</td>
</tr>
<tr>
<td>컨테이너 내부 cron</td>
<td>간단</td>
<td>Dockerfile 수정, 권장하지 않음</td>
<td>❌</td>
</tr>
<tr>
<td>Edge Function + pg_cron</td>
<td>인프라 불필요, Supabase가 실행</td>
<td>-</td>
<td>✅</td>
</tr>
</tbody></table>
<p>더 자세히 보면</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>컨테이너 cron</th>
<th>Next.js API + 외부 cron</th>
<th>pg_cron (현재)</th>
</tr>
</thead>
<tbody><tr>
<td>설정 복잡도</td>
<td>중간 (Dockerfile 수정)</td>
<td>중간 (API Route + 외부 서비스)</td>
<td>낮음 (SQL만)</td>
</tr>
<tr>
<td>안정성</td>
<td>낮음 (컨테이너 의존)</td>
<td>중간 (외부 서비스 의존)</td>
<td>높음 (DB 레벨)</td>
</tr>
<tr>
<td>스케일링</td>
<td>문제 있음 (중복 실행)</td>
<td>문제 있음 (외부 호출 중복 가능)</td>
<td>문제 없음</td>
</tr>
<tr>
<td>로그 관리</td>
<td>어려움</td>
<td>중간 (Next.js 로그)</td>
<td>쉬움 (DB 쿼리)</td>
</tr>
<tr>
<td>재시작 영향</td>
<td>있음</td>
<td>없음 (외부 호출)</td>
<td>없음</td>
</tr>
<tr>
<td>인프라 의존성</td>
<td>컨테이너</td>
<td>외부 cron 서비스</td>
<td>Supabase</td>
</tr>
<tr>
<td>비용</td>
<td>없음</td>
<td>무료/유료 서비스</td>
<td>없음</td>
</tr>
<tr>
<td>보안</td>
<td>내부</td>
<td>외부 HTTP 노출</td>
<td>내부</td>
</tr>
<tr>
<td>실행 이력 추적</td>
<td>어려움</td>
<td>외부 서비스에 의존</td>
<td>쉬움 (DB 쿼리)</td>
</tr>
</tbody></table>
<h4 id="edge-function--pg_cron-선택-이유">Edge Function + pg_cron 선택 이유</h4>
<ol>
<li>인프라 설정 불필요<ul>
<li>별도 서버/스케줄러 없음</li>
<li>pg_cron만 설정하면 자동 실행</li>
</ul>
</li>
<li>형상관리 가능<ul>
<li>Edge Function 코드를 Git에 포함</li>
<li>이전 Edge Function은 코드베이스에 없어 관리 어려움</li>
</ul>
</li>
<li>간단한 설정<ul>
<li>SQL 한 번 실행으로 완료</li>
<li>Dashboard에서 환경 변수만 설정</li>
</ul>
</li>
<li>자동 실행<ul>
<li>Supabase가 스케줄링 처리</li>
<li>모니터링/로그 확인 용이</li>
</ul>
</li>
</ol>
<hr>
<h3 id="db-function과-edge-funciton---요즘은-안쓴다">DB Function과 Edge Funciton -&gt; 요즘은 안쓴다?</h3>
<p>형상 관리가 어려워서 좀 복잡해지는 한이 있더라도 codebase로 옮기는 추세
slack 알림 연동 기능을 처음엔 edge function으로 작성했으나 코드로 마이그레이션 했음
gemini 코드 리뷰에서 원자성 보장을 위해 db function쓰는거 생각해보라 했지만
최대한 서버액션으로 처리하는걸로 결정</p>
<p>그러나... 주기적 알림을 추가 인프라를 만지지 않고 구현하려다보니
pg_cron을 사용하게 되면서 edge function을 다시 쓰게 되긴 했다</p>
<hr>
<h3 id="supabase-cli로-edge-function-배포하기">supabase cli로 edge function 배포하기</h3>
<p>직접 사이트에서 edge function을 deploy해왔었는데 구글링하다가 발견..
공식 문서를 잘 읽읍시다
<a href="https://supabase.com/docs/guides/functions/deploy">https://supabase.com/docs/guides/functions/deploy</a>
post 401 에러에 마주쳤는데 이건 jwt 인증 끄고 해결..?
<a href="https://github.com/orgs/supabase/discussions/33194">https://github.com/orgs/supabase/discussions/33194</a>
이건 좀 위험하다해서 cron secret을 헤더에 추가해서 검증...?
사실 이해가 좀 덜 되긴 했다.
sql문 지금까지 실행하면서 policy / db함수 / edge function 만든 거 다시봐야될 것 같다.</p>
<pre><code class="language-sql">SELECT cron.schedule(
  &#39;check-reminders&#39;,
  &#39;*/5 * * * *&#39;,  -- 매 5분마다
  $$
  SELECT
    net.http_post(
      url := &#39;https://프로젝트코드.supabase.co/functions/v1/함수이름&#39;,
      headers := jsonb_build_object(
        &#39;Content-Type&#39;, &#39;application/json&#39;,
        &#39;x-cron-secret&#39;, &#39;임의의 값&#39; 
      ),
      body := &#39;{}&#39;::jsonb
    ) AS request_id;
  $$
);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[최근의 근황]]></title>
            <link>https://velog.io/@squee_z_e/%EC%B5%9C%EA%B7%BC%EC%9D%98-%EA%B7%BC%ED%99%A9</link>
            <guid>https://velog.io/@squee_z_e/%EC%B5%9C%EA%B7%BC%EC%9D%98-%EA%B7%BC%ED%99%A9</guid>
            <pubDate>Sat, 01 Nov 2025 14:13:18 GMT</pubDate>
            <description><![CDATA[<p>정글 생활을 마치고 취업 준비 활동을 이어가던 중,
정글에서 지원해준 기회로 한 회사에 풀스택 개발자 인턴으로 합류하게 되었다.
이번주가 출근 첫 주였음!</p>
<p>지금 당장 해야할 일들이 많이 쌓여있지만
너무 집중이 안되니까 기록이라도 하나 남겨놓으려고 오랜만에 블로그에 방문했다.
사실 그냥 조잘조잘 떠들 공간이 필요했답니다</p>
<p>출근 첫날엔 전반적인 회사생활에 대한 온보딩 교육, 앞으로 진행할 프로젝트에 대한 온보딩 교육을 들었다.
배정받은 장비로 작업 환경을 세팅했는데, 처음으로 맥북을 사용하게 돼서 적응하는게 쉽지 않았다.
(요즘은 조금 적응했는데, 회사에서는 macOS 쓰고 집에서는 window 쓰니까 자꾸 키가 헷갈려서 킹받는다.
지금도 한영키 대신 caps lock, ctrl대신 fn 키 눌러대는 중)</p>
<p>업무 적응을 위해 일주일 간 토이 프로젝트를 진행하게 됐다.
회사에서 사용하고 있는 그룹웨어에서 좀 불편하게 되어 있는 기능이 하나 있는데, 
그걸 확실히 개선한 시스템을 만들어서 사내망에 배포할 예정이다.
하루만에 프로젝트 범위 잡고 ERD, flow chart 작성 후, 수~금 3일간 개발을 진행했다.
처음에는 간단해보였지만, 생각보다 쉽지 않다.
express, prisma, supabase, adminjs, react // potainer, nginx proxy manager, github action</p>
<p>이해가 안되는 게 많아도 어떻게든 기간안에 뭘 만드려고 바이브 코딩을 해댔는데,
회사에서 지원해준 cursor 팀 플랜에서 기본제공 20달러 + 청구형 50달러 사용을 달성했다.
금요일 퇴근할 때 쯤에 팀장님께서 언질주셔서 확인해보게 됐는데 <strong>ㄹㅇ 대충격 먹음</strong>
비싼 모델 막 쓴 게 문제가 된 것 같은데, 3일만에 70달러는 좀 아니잖아 징짜
cursor pro 결제해서 썼을때는 auto로 놓고 썼고 추가비용 들일도 없어서 너무 안일했다.
월급에서 깐다고 농을 하셨는데 눈치가 엄청 보였다. 
AI를 보조도구로만 사용해야하는 이유가 금액적인 부분에도 있는 듯
실제 프로젝트 투입되면 spring 사용할 것 같던데 더 걱정이다.</p>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/dd32e9fa-5d7e-4d41-9346-9af55b03b5af/image.png" alt=""></p>
<p>내 개발 실력을 키우는 것이 해결방안임이 자명함에도.... 
일단 인턴 생활 중이니 업무 투입됐을 때 도움이 되어야 한다는 생각과
생산성 측면에서 AI를 안쓰는 건 불가능하다는 생각때문에 claude code, cursor 유료 플랜들을 막 찾아봤다.
개인적으로 이용중이던 chatGPT, cursor의 20$ 플랜 결제 중단하고
<strong>조만간 claude code의 100$ 플랜을 결제하게 될 것 같다^.^</strong> 개비싸 진짜
IDE가 편하다고 머무르려고 하지 말고 더 성능좋고 가성비 좋은 걸 사용하자..
회사의 지원이 어렵다면, 많은 걸 경험하고 배우는 입장이니 혼자서라도 사서 여러 방면에 사용해보자...
프롬프트 효율적으로 작성하는 법이랑 비용 아끼는 법들도 있으니 많이 찾아보자....</p>
<p>회사는 위치나 업무환경이 너무 좋고, 사람들도 좋다.
데일리 스크럼 참여하면서 뭐라도 한마디 내뱉는 게 아직은 어색하지만 개발팀 일원이 된 것 같아 기분이 미묘하다.
일이 많을 것 같긴 하지만, 바로 주요 업무에 투입되면서 배우는 부분이 엄청 많을 것 같다.
다만 어리벙벙한 나 때문에 동기가 고통받을 것 같아 미리 죄송함</p>
<p>공부를 계속하면서도 느끼는건데 개발자들은 정말 대단하다
저도 업계에서 살아남고 싶어요 흑흑 열심히 해볼게요</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[나만무] 6/16. Spring Boot 기초]]></title>
            <link>https://velog.io/@squee_z_e/Spring-boot-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@squee_z_e/Spring-boot-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Mon, 16 Jun 2025 11:54:22 GMT</pubDate>
            <description><![CDATA[<p>블로그 구현 연습에 <strong>스프링부트 3 백엔드 개발자 되기</strong> 책을 참고했다.</p>
<hr>
<h3 id="스프링-핵심-개념-4가지">스프링 핵심 개념 4가지</h3>
<ul>
<li>IoC (제어의 역전): 객체 생성과 제어 권한을 개발자 대신 스프링이 맡는 구조.</li>
<li>DI (의존성 주입): 필요한 객체를 직접 만들지 않고, 스프링이 대신 넣어주는 방식.</li>
<li>AOP (관점 지향 프로그래밍): 핵심 로직과 공통 기능(로깅, 보안 등)을 깔끔히 분리하는 기법.</li>
<li>PSA (서비스 추상화): 다양한 기술(JDBC, JMS 등)을 추상화해 구현체가 바뀌어도 코드 변경을 최소화함.</li>
</ul>
<hr>
<h3 id="애너테이션annotation"><strong>애너테이션(Annotation)</strong></h3>
<p>코드에 붙이는 <strong>메타데이터(정보)</strong>로, <strong>스프링이 그걸 보고 자동으로 동작을 설정</strong>함.</p>
<h3 id="🔹-자주-쓰는-애너테이션-요약">🔹 자주 쓰는 애너테이션 요약</h3>
<table>
<thead>
<tr>
<th>애너테이션</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><code>@Component</code></td>
<td>일반 객체를 스프링 빈으로 등록</td>
</tr>
<tr>
<td><code>@Service</code></td>
<td>비즈니스 로직 클래스 등록 (Component 역할 + 의미 부여)</td>
</tr>
<tr>
<td><code>@Repository</code></td>
<td>DB 접근 클래스 등록, 예외 처리 자동화 포함</td>
</tr>
<tr>
<td><code>@Controller</code></td>
<td>웹 요청을 처리하는 클래스 등록</td>
</tr>
<tr>
<td><code>@RestController</code></td>
<td><code>@Controller + @ResponseBody</code>, JSON 응답용</td>
</tr>
<tr>
<td><code>@Autowired</code></td>
<td>필요한 객체를 자동 주입</td>
</tr>
<tr>
<td><code>@Bean</code></td>
<td>개발자가 직접 빈 등록</td>
</tr>
<tr>
<td><code>@Configuration</code></td>
<td>설정 클래스 지정</td>
</tr>
<tr>
<td><code>@RequestMapping</code></td>
<td>URL 요청을 특정 메서드에 매핑</td>
</tr>
</tbody></table>
<hr>
<h3 id="스프링-부트-3-구조">스프링 부트 3 구조</h3>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/d544ba81-faed-4e44-8039-b0bb9973230d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/squee_z_e/post/19bafc1d-899b-4808-a487-afdd92e20f83/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[나만무] 6/13. 프레임워크 학습 시작]]></title>
            <link>https://velog.io/@squee_z_e/%EB%82%98%EB%A7%8C%EB%AC%B4-613.-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%ED%95%99%EC%8A%B5-%EC%8B%9C</link>
            <guid>https://velog.io/@squee_z_e/%EB%82%98%EB%A7%8C%EB%AC%B4-613.-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%ED%95%99%EC%8A%B5-%EC%8B%9C</guid>
            <pubDate>Thu, 12 Jun 2025 17:50:48 GMT</pubDate>
            <description><![CDATA[<p>나만무 기간동안은 매일 TIL을 적어볼 예정이다.</p>
<p>웹 개발 팀을 꾸리기 전, 역량 개발을 위한 일주일이 주어졌다.
파이썬과 C는 해봤지만 Java는 처음이라 두렵다.. 
짧은 시간 안에 흐름과 기술 기초를 익혀둬야 나중에 팀에 도움이 될 수 있으니 열심히 해보자.</p>
<p>오늘은 계획 짜고 개발 환경 세팅했다.
싹다 경험해보자 그냥...</p>
<p>IDE - cursor, intelij, vs code
AI - chatGPT, copilot, gemini, claude
front - HTML, CSS, JavaScript / React.js
back - Java / Spring Boot / MySQL
도구 - GitHub, Docker, AWS, Figma(+makereal tldraw)</p>
<hr>
<h3 id="주요-기술">주요 기술</h3>
<h4 id="🖥️-프론트엔드">🖥️ 프론트엔드</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>기술</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>언어</strong></td>
<td>HTML, CSS, JavaScript, TypeScript</td>
<td>웹 UI 구현의 기초</td>
</tr>
<tr>
<td><strong>UI 프레임워크</strong></td>
<td>React.js, Vue.js, Angular</td>
<td>컴포넌트 기반 개발</td>
</tr>
<tr>
<td><strong>서버 사이드 렌더링</strong></td>
<td>Next.js (React), Nuxt.js (Vue)</td>
<td>SEO와 초기 로딩속도에 유리</td>
</tr>
<tr>
<td><strong>스타일링/애니메이션</strong></td>
<td>Tailwind CSS, Bootstrap, GSAP</td>
<td>UI/UX 향상</td>
</tr>
<tr>
<td><strong>기타 도구</strong></td>
<td>jQuery, Vanilla JS</td>
<td>간단한 DOM 조작 시 사용 가능</td>
</tr>
</tbody></table>
<h4 id="⚙️-백엔드">⚙️ 백엔드</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>기술</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>언어</strong></td>
<td>Java, JavaScript/TypeScript (Node.js), Kotlin</td>
<td>다양한 서버 언어</td>
</tr>
<tr>
<td><strong>프레임워크</strong></td>
<td>Spring Boot, Spring Framework</td>
<td>Java 플랫폼의 표준 웹 백엔드</td>
</tr>
<tr>
<td></td>
<td>Express.js, Nest.js</td>
<td>Node.js 기반 REST API 중심 프레임워크</td>
</tr>
<tr>
<td><strong>보안/인증</strong></td>
<td>Spring Security, JWT, OAuth2</td>
<td>인증·권한 관리</td>
</tr>
<tr>
<td><strong>ORM / DB 연동</strong></td>
<td>JPA/Hibernate, MyBatis</td>
<td>관계형 DB 연동 지원</td>
</tr>
<tr>
<td><strong>DB</strong></td>
<td>MySQL, PostgreSQL, Oracle, MSSQL, MariaDB</td>
<td>RDBMS</td>
</tr>
<tr>
<td></td>
<td>MongoDB, Redis, Elasticsearch</td>
<td>NoSQL, 캐싱, 검색 엔진 등</td>
</tr>
</tbody></table>
<h4 id="인프라--배포-등-기타-기술">인프라 / 배포 등 기타 기술</h4>
<ul>
<li><strong>CI/CD</strong>: Jenkins, GitHub Actions — 빌드 &amp; 배포 자동화  </li>
<li><strong>컨테이너화</strong>: Docker — 환경 일관성 적용  </li>
<li><strong>오케스트레이션</strong>: Kubernetes — 대규모 서비스 운영  </li>
<li><strong>클라우드 플랫폼</strong>: AWS, GCP, Azure — 확장성 있는 서비스 배포  </li>
<li><strong>디자인/협업 툴</strong>: Figma — UI/UX 협업에 유용  </li>
</ul>
<h4 id="요약">요약</h4>
<ul>
<li><strong>프론트엔드</strong>는 HTML/CSS/JS 기반 위에 React, Vue 같은 프레임워크와 스타일링 도구를 쓰면 UI 구성에 빠르고 편리하다.</li>
<li><strong>백엔드</strong>는 Java(Spring)나 Node.js(Express/Nest) 환경에서 ORM과 DB를 연결해 API 형태로 기능을 제공한다.</li>
<li><strong>인프라</strong>나 <strong>CI/CD</strong>, <strong>컨테이너</strong> 관련 도구는 실제 서비스 운영/배포 단계에서 도움을 준다.</li>
</ul>
<hr>
<h3 id="📅-개인-과제-개요-및-계획">📅 개인 과제 개요 및 계획</h3>
<blockquote>
<p>[기능 요구사항]
회원가입/로그인
게시물 작성
게시물 목록보기
게시물 읽기
댓글 작성</p>
</blockquote>
<blockquote>
<p>[개발범위]
프론트: 게시판 UI를 구현
백엔드: 게시판 Server API 구현
추가적 구현 가능 (기술적 챌린지 요소)</p>
</blockquote>
<blockquote>
<p>[인원구성 (다음중 한가지 선택)]
혼자 fullstack 개발 가능
프론트 UI만 구현할 경우
게시판 데이터는 브라우저상에서만 존재하도록 구성
또는 postman mock server를 구성해서 사용
백엔드 ServerAPI만 구현한 경우
postman등으로 요청을 직접 보내서 시연<br>또는 원하는 사람과 2인팀을 구성하고, 프론트/백엔드를 나누어서 개발</p>
</blockquote>
<h4 id="📍-day-1---java--객체지향-기초-다지기">📍 Day 1 - Java &amp; 객체지향 기초 다지기</h4>
<ul>
<li>Java 기본 문법 학습 (변수, 조건문, 반복문 등)</li>
<li>객체지향 개념 정리 (클래스, 상속, 다형성, 캡슐화 등)</li>
<li>IntelliJ 셋업 및 사용법 익히기</li>
<li>콘솔 게시판 간단 구현 (객체지향 감 잡기용)</li>
</ul>
<h4 id="📍-day-2---spring-boot로-게시판-백엔드-뼈대-만들기">📍 Day 2 - Spring Boot로 게시판 백엔드 뼈대 만들기</h4>
<ul>
<li>Spring Boot 프로젝트 생성 (Spring Initializr)</li>
<li>기본적인 REST API 구현 (회원가입, 게시글 등록 등)</li>
<li>H2 DB 연결 및 JPA 사용해보기</li>
<li>Postman으로 API 테스트</li>
<li>MVC 구조 익히기 (Controller, Service, Repository)</li>
<li><strong>핵심 개념</strong>: REST API, MVC, 의존성 주입</li>
</ul>
<h4 id="📍-day-3---react-기본-학습--프론트-구조-잡기">📍 Day 3 - React 기본 학습 &amp; 프론트 구조 잡기</h4>
<ul>
<li>React 앱 생성 (Vite)</li>
<li>JSX, 컴포넌트 구조, props, useState 등 기본기 익히기</li>
<li>게시판 UI 구성 시작 (게시글 목록, 작성 폼 등)</li>
<li>Axios로 백엔드 API 연동 테스트</li>
<li><strong>핵심 개념</strong>: 컴포넌트 분리, 상태 관리, 이벤트 처리</li>
</ul>
<h4 id="📍-day-4---프론트-백-연동--주요-기능-완성">📍 Day 4 - 프론트-백 연동 &amp; 주요 기능 완성</h4>
<ul>
<li>로그인 / 회원가입 기능 연결</li>
<li>게시글 작성 / 조회 / 상세보기 구현</li>
<li>댓글 등록 기능 구현</li>
<li>전체 기능 연동 테스트</li>
<li><strong>중점 포인트</strong>: API 연결 흐름 이해, CORS 설정, 에러 처리</li>
</ul>
<h4 id="📍-day-5---디버깅--마무리">📍 Day 5 - 디버깅 &amp; 마무리</h4>
<ul>
<li>기능별 예외 처리 추가</li>
<li>프론트 간단한 CSS 정리</li>
<li>GitHub 업로드 및 README 작성</li>
<li>선택: 로컬 배포 or GitHub Pages/Render 사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[pintOS 주간 회고]]></title>
            <link>https://velog.io/@squee_z_e/pintOS-%EC%A3%BC%EA%B0%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@squee_z_e/pintOS-%EC%A3%BC%EA%B0%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 12 Jun 2025 17:10:50 GMT</pubDate>
            <description><![CDATA[<p>길고 긴... 지옥의 주간이 끝났다.</p>
<ul>
<li>지금 쓰긴 귀찮아서 내일 수정 예정</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[week12~13. pintOS(3)]]></title>
            <link>https://velog.io/@squee_z_e/week1213.pintos3</link>
            <guid>https://velog.io/@squee_z_e/week1213.pintos3</guid>
            <pubDate>Thu, 12 Jun 2025 17:07:53 GMT</pubDate>
            <description><![CDATA[<p>Virtual Memory
Page Table
Translation Lookaside Buffer (TLB)
Page Fault
Lazy Loading
Page Replacement Policy
Anonymous page
Swap Disk
File-backed Page
Direct Memory Access</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[week10~11. pintOS(2)]]></title>
            <link>https://velog.io/@squee_z_e/week1011.-pintOS2</link>
            <guid>https://velog.io/@squee_z_e/week1011.-pintOS2</guid>
            <pubDate>Tue, 27 May 2025 15:15:38 GMT</pubDate>
            <description><![CDATA[<p>User mode vs Kernel mode
Register vs Memory
User Stack
System Call
File Descriptor
Cache
Atomic Operation
rax register
32 bit OS vs 64 bit OS
Interrupt
Segmentation Fault</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[week09. pintOS(1)]]></title>
            <link>https://velog.io/@squee_z_e/week09.-pintos1</link>
            <guid>https://velog.io/@squee_z_e/week09.-pintos1</guid>
            <pubDate>Thu, 15 May 2025 05:44:36 GMT</pubDate>
            <description><![CDATA[<p>Process, Thread</p>
<p>CPU Scheduling 알고리즘</p>
<pre><code>FCFS (First Come First Served)
SJF (Shortest Job First)
SRTF (Shortest Remaining Time First)
Round Robin
Multilevel Queue Scheduling</code></pre><p>Semaphore와 Mutex
Race Condition
Deadlock
Context Switching
Multi-Level Feedback Queue Scheduler (MLFQS)</p>
<p>Project 1</p>
<pre><code>Chapter 3. Machine-Level Representation of Programs
Chapter 12. Concurrent Programming</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[week08. 웹서버 만들기]]></title>
            <link>https://velog.io/@squee_z_e/week08.-%EC%9B%B9%EC%84%9C%EB%B2%84-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@squee_z_e/week08.-%EC%9B%B9%EC%84%9C%EB%B2%84-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 08 May 2025 14:00:43 GMT</pubDate>
            <description><![CDATA[<p>네트워크 계층 (OSI7 Layer, TCP/IP Layer)웹페이지</p>
<p>클라이언트-서버 모델 웹페이지</p>
<p>소켓(socket, bind, listen, accept, connect, close)웹페이지</p>
<p>파일 디스크립터 웹페이지</p>
<p>Datagram Socket vs Stream Socket웹페이지</p>
<p>CGI / WebServer / MIME Type웹페이지</p>
<p>HTTP (요청/응답, 헤더, 메소드, 상태코드, HEAD 메소드)웹페이지</p>
<p>Proxy</p>
<p>BSD소켓, IP, TCP, HTTP, file descriptor, DNS</p>
]]></description>
        </item>
    </channel>
</rss>