이해람
6년차 프론트엔드 엔지니어로 30년의 업력을 가진 UI 프레임워크 개발 기업에서 기본기를 다진 뒤 헬스케어 스타트업에서 의료진 / 환자 / 고령자 대상의 애플리케이션을 개발/유지보수 하였습니다.
아키텍처에 정답은 없다고 생각하며 프로젝트의 목적, 개발팀의 리소스, 개발 기한을 고려하여 가장 적합한 아키텍처를 설계하고 있습니다.
저는 가장 중요한 업무에 가장 큰 에너지를 사용해야 한다는 원칙을 중요하게 생각합니다. 함께 일하는 동료들 역시 그렇게 할 수 있도록, 각자의 성향을 파악해 불필요한 감정 소모나 의사소통 피로감을 최소화하고 갈등을 해결하는 것에 강점이 있습니다.
AI는 사용하는 사람에 따라 생산성이 달라지기 때문에 AI 룰 작성이나 프롬프트 공유 등 AI를 활용한 팀 생산성 높이기에 많은 고민을 합니다.
근감소증 치료를 위한 디지털 치료기기(DTx)
DDD 기반 개발 전략의 부작용
User Story별로 Fractal Cell을 나누고 브랜치를 분리하여 개발 후 develop 브랜치로 병합
역할 중심 아키텍처의 한계
components/, pages/, hooks/ 등 역할별 폴더 구조로 인해 여러 팀원이 동일한 디렉토리를 수정하며 충돌 발생
export function ExerciseCard({ exercise }: Props) {  const { data: records } = useExerciseRecords(exercise.id);    return (    <div className="card">      <h3>{exercise.name}</h3>      <p>{exercise.description}</p>            {/* 개발자 A가 추가한 코드 */}      <div className="records">        {records?.map(r => (          <RecordItem key={r.id} record={r} />        ))}      </div>    </div>  );}export function ExerciseCard({ exercise }: Props) {  const { data: records } = useExerciseRecords(exercise.id);    return (    <div className="card">      <h3>{exercise.name}</h3>      <p>{exercise.description}</p>            {/* 개발자 A가 추가한 코드 */}      <div className="records">        {records?.map(r => (          <RecordItem key={r.id} record={r} />        ))}      </div>    </div>  );}export function ExerciseCard({ exercise }: Props) {  const difficulty = calculateDifficulty(exercise);    return (    <div className="card">      <h3>{exercise.name}</h3>      <p>{exercise.description}</p>            {/* 개발자 B가 추가한 코드 */}      <div className="difficulty">        <DifficultyBadge level={difficulty} />      </div>    </div>  );}// Feature: 운동 기록 조회import { ExerciseCard } from '@/entities/exercise';import { useExerciseRecords } from '../api/useExerciseRecords'; export function ExerciseRecordView({ exerciseId }: Props) {  const { data: records } = useExerciseRecords(exerciseId);    return (    <div className="space-y-4">      <ExerciseCard exerciseId={exerciseId} />            <div className="records-section">        <h4>운동 기록</h4>        {records?.map(record => (          <RecordItem key={record.id} record={record} />        ))}      </div>    </div>  );}// Feature: 운동 기록 조회import { ExerciseCard } from '@/entities/exercise';import { useExerciseRecords } from '../api/useExerciseRecords'; export function ExerciseRecordView({ exerciseId }: Props) {  const { data: records } = useExerciseRecords(exerciseId);    return (    <div className="space-y-4">      <ExerciseCard exerciseId={exerciseId} />            <div className="records-section">        <h4>운동 기록</h4>        {records?.map(record => (          <RecordItem key={record.id} record={record} />        ))}      </div>    </div>  );}// Feature: 운동 난이도 표시import { ExerciseCard } from '@/entities/exercise';import { useDifficulty } from '../model/useDifficulty'; export function ExerciseDifficultyView({ exerciseId }: Props) {  const { difficulty } = useDifficulty(exerciseId);    return (    <div className="space-y-4">      <ExerciseCard exerciseId={exerciseId} />            <div className="difficulty-section">        <h4>운동 난이도</h4>        <DifficultyBadge level={difficulty} />        <DifficultyDescription level={difficulty} />      </div>    </div>  );}Sprint에서 구현 할 User Stroy를 정확하게 정의 후 컴포넌트 설계
Rule: 1 User Story = 1 Feature = 1 Branch
Feature 성격에 맞지 않는 코드를 적절한 레이어로 이동
FSD 레이어별로 명확한 규칙을 수립하고 AI 룰을 작성하여 팀 전체가 활용
화병 치료를 위한 디지털 치료기기(DTx)
리소스 제약 속 협업
사이드 프로젝트로 리소스가 제한된 상황에서 Figma에 익숙하지 않은 영상 디자이너와 앱 개발 진행
디자인 전달의 어려움
초기 디자인이 개발하기 어려운 형태로 전달되어 반복적인 수정 필요
"어떻게 주셔도 개발할 수 있으니 괜찮아요"라며 디자이너가 도전할 수 있도록 동기부여
"이렇게 하면 안 돼요" 대신 "이렇게 해주시면 개발이 더 수월해요"로 구체적 개선 방향 제시
디자이너와 활발히 소통하며 함께 앱의 사용자 경험을 개선
고령자 대상 운동 애플리케이션
개발 철학의 부재
명확한 아키텍처 패턴이 없어 개발자마다 비즈니스 로직을 다른 위치에 작성
유지보수의 어려움
코드의 일관성 저하로 동료 간 코드 이해와 유지보수에 불필요한 비용 발생
class ExerciseScreen extends StatelessWidget {  final ExerciseRepository repository;    @override  Widget build(BuildContext context) {    return FutureBuilder<List<Exercise>>(      future: repository.getExercises(),      builder: (context, snapshot) {        if (!snapshot.hasData) {          return CircularProgressIndicator();        }                return ListView.builder(          itemCount: snapshot.data!.length,          itemBuilder: (context, index) {            return ExerciseCard(              exercise: snapshot.data![index],            );          },        );      },    );  }}class ExerciseScreen extends StatelessWidget {  final ExerciseRepository repository;    @override  Widget build(BuildContext context) {    return FutureBuilder<List<Exercise>>(      future: repository.getExercises(),      builder: (context, snapshot) {        if (!snapshot.hasData) {          return CircularProgressIndicator();        }                return ListView.builder(          itemCount: snapshot.data!.length,          itemBuilder: (context, index) {            return ExerciseCard(              exercise: snapshot.data![index],            );          },        );      },    );  }}class ExerciseRepositoryImpl implements ExerciseRepository {  final RemoteDataSource remoteDataSource;  final LocalDataSource localDataSource;    @override  Future<List<Exercise>> getExercises() async {    try {      // 원격 데이터 먼저 시도      final exercises = await remoteDataSource.fetchExercises();            // 로컬에 캐싱      await localDataSource.cacheExercises(exercises);            return exercises;    } catch (e) {      // 실패 시 로컬 데이터 사용      return await localDataSource.getCachedExercises();    }  }}어르신 복지관 방문 시 앱 설치 역할에서 더 나아가 직접 사용자 인터뷰 진행
고령자 대상 운동 애플리케이션 MVP 버전
Flutter 경험 전무
React 기반 웹 서비스 유지보수 중, 신규 앱 프로젝트를 Flutter로 개발해야 하는 상황
빠른 MVP 개발 필요
프로덕트 출시를 위해 단기간 내 학습과 개발을 병행해야 함
"프로덕트의 성공을 위해서는 기술 스택이 장벽이 되어서는 안 된다"
Dart 언어와 Flutter 생태계를 즉시 학습 시작. 기존 모바일 앱 개발 경험과 웹 개발 지식을 활용
이론 학습과 동시에 MVP 개발을 진행하며 실전적인 문제 해결에 집중
기술 자체보다 사용자와 프로덕트의 가치를 우선순위로 두고 개발
© 2025 Haeram Lee. Built with React Router 7