이번 프로젝트에서 견적 문의 기능을 만들면서 가장 고민했던 부분은 “가격 옵션을 어떻게 관리할 것인가”였다.
처음에는 모든 견적 옵션을 프론트엔드 코드에 직접 작성하는 방식도 생각했다. 음식 서빙 도우미, 수의, 관, 차량, 부가 서비스처럼 정해진 옵션들이 있었기 때문에 단순히 배열로 만들어도 구현 자체는 어렵지 않았다.
하지만 실제 운영을 생각하면 이야기가 달라졌다.
장례 서비스 특성상 단가가 바뀔 수 있고, 옵션명이 수정될 수도 있다. 만약 이런 값들이 전부 코드에 하드코딩되어 있다면, 가격 하나를 바꿀 때마다 개발자가 코드를 수정하고 다시 배포해야 한다.
클라이언트는 개발자가 아니기 때문에 운영 중에 직접 수정할 수 있는 구조가 필요했다. 그래서 견적 옵션은 Google Sheet에서 관리하고, 사이트에서는 해당 데이터를 불러와 화면에 보여주는 방식으로 설계했다.
견적 옵션을 관리하기 쉽게 만든 이유
이 기능의 핵심은 “비개발자도 수정할 수 있어야 한다”였다.
관리자가 별도의 관리자 페이지에 로그인해서 옵션을 수정하는 구조를 만들 수도 있었다. 하지만 이 프로젝트에서는 견적 옵션 관리만을 위해 복잡한 관리자 기능을 추가하는 것보다, 이미 익숙한 Google Sheet를 활용하는 편이 더 현실적이라고 판단했다.
Google Sheet를 사용하면 운영자는 엑셀처럼 값을 수정할 수 있다.
예를 들어 가격을 바꾸고 싶다면 price 컬럼의 숫자만 수정하면 된다. 옵션명을 바꾸고 싶다면 name 컬럼을 수정하면 된다. 새로운 옵션을 추가하고 싶다면 행을 하나 더 추가하면 된다.
구조는 단순하게 잡았다.
category | name | price
helper | 1인 추가 (8시간) | 110000
coffin | 특관 | 200000
service | 꽃관 | 150000
여기서 중요한 점은 컬럼명을 고정하는 것이었다.
프론트와 백엔드는 category, name, price라는 컬럼명을 기준으로 데이터를 읽는다. 그래서 운영자에게는 “컬럼명은 절대 바꾸지 말고, 아래 데이터만 수정해야 한다”고 안내했다.
이렇게 하면 개발자는 구조를 안정적으로 유지할 수 있고, 운영자는 필요한 내용만 쉽게 수정할 수 있다.
Google Sheet API를 견적 데이터 소스로 사용하기
Google Sheet를 일반적인 DB처럼 사용하기 위해서는 시트 데이터를 API로 불러와야 했다.
처음에는 일반 Google Sheets API를 사용할 수도 있다고 생각했다. 하지만 인증 설정이나 서비스 계정 관리까지 들어가면 초기 작업이 복잡해질 수 있었다.
그래서 견적 옵션처럼 민감정보가 없는 데이터는 Google Visualization API를 활용하는 방식으로 접근했다.
대략적인 호출 구조는 다음과 같다.
<https://docs.google.com/spreadsheets/d/{SHEET_ID}/gviz/tq?tqx=out:json&sheet={SHEET_TITLE}>
여기서 필요한 값은 두 가지다.
SHEET_ID: 구글 스프레드시트 문서 ID
SHEET_TITLE: 데이터를 읽을 시트 탭 이름
사이트에서는 이 URL로 데이터를 요청하고, 응답받은 내용을 파싱해서 견적 옵션 목록으로 변환한다.
다만 Google Visualization API는 일반적인 JSON을 그대로 반환하지 않는다. 응답이 아래처럼 감싸져 있다.
/*O_o*/
google.visualization.Query.setResponse({
// 실제 데이터
});
그래서 그대로 JSON.parse()를 하면 오류가 난다.
이 부분이 생각보다 중요한 포인트였다. API 응답을 받아서 앞뒤에 붙은 문자열을 제거한 뒤, 실제 JSON 부분만 파싱해야 했다.
한글 깨짐과 JSON 파싱 문제
Google Sheet 연동을 하면서 가장 먼저 마주친 문제는 데이터 파싱이었다.
응답이 일반 JSON이 아니기 때문에 문자열을 잘라내는 처리가 필요했고, 잘못 자르면 바로 파싱 오류가 발생했다.
처음에는 단순히 앞의 47글자, 뒤의 2글자를 잘라내는 방식으로 처리했다. Google Visualization API 응답 형식이 일정하기 때문에 동작은 했지만, 운영 환경에서는 조금 더 안정적인 방식이 필요하다고 느꼈다.
그래서 단순 substring에만 의존하지 않고, 응답 문자열에서 JSON이 시작하는 { 위치와 끝나는 } 위치를 찾아 파싱하는 방식도 고려했다.
핵심은 이런 흐름이다.
1. Google Sheet API 응답을 문자열로 받는다
2. 실제 JSON 객체가 시작되는 위치를 찾는다
3. 마지막 JSON 객체가 끝나는 위치를 찾는다
4. 해당 부분만 잘라 JSON.parse()를 실행한다
5. rows 데이터를 category, name, price 형태로 변환한다
또 하나 신경 쓴 부분은 한글 데이터였다.
옵션명은 대부분 한글이었다. “1인 추가”, “꽃관”, “리무진”, “미선택” 같은 값이 깨지면 화면에서 바로 티가 난다. 실제로 테스트 과정에서 한글이 깨져 보이는 문제가 있었고, 데이터가 어떤 방식으로 읽히는지 확인해야 했다.
결국 시트의 데이터 형식과 API 응답을 확인하면서, 프론트와 백엔드에서 문자열을 무리하게 변환하지 않고 그대로 사용하도록 정리했다.
Google Sheet 쪽에서는 컬럼명을 영문 소문자로 고정하고, 실제 표시되는 name 값만 한글로 관리하는 방식으로 정리했다.
예외 처리를 넣은 이유
외부 API를 사용하는 기능은 항상 실패 가능성을 생각해야 한다.
Google Sheet는 편리하지만, 사이트 입장에서는 외부 서비스다. 네트워크 문제, 권한 문제, 시트 이름 변경, 컬럼명 변경, 잘못된 가격 입력 같은 상황이 생길 수 있다.
그래서 견적 옵션을 불러오는 부분에는 예외 처리가 필요했다.
예를 들어 Google Sheet API 호출에 실패하면 화면이 깨지는 대신 사용자에게 안내 문구를 보여주도록 했다.
네트워크 연결을 확인해 주세요.
급하신 경우 전화로 문의 가능합니다.
또 가격 데이터도 그대로 믿으면 안 된다.
price 값이 비어 있거나 숫자가 아닌 문자열로 들어올 수도 있다. 이 경우 계산 과정에서 NaN이 발생하면 총액이 깨질 수 있다.
그래서 가격은 항상 숫자로 변환하고, 변환할 수 없는 값은 0으로 처리했다.
Number(value || 0)
이런 처리를 넣으면서 운영자가 실수로 빈 값을 넣더라도 사이트 전체가 망가지지 않게 했다.
완벽한 방어는 아니지만, 실제 운영에서 자주 생길 수 있는 실수를 최소한으로 흡수하는 장치라고 생각했다.
옵션 선택 시 실시간 총액 계산
견적 기능의 사용자 흐름은 단순해야 했다.
사용자가 옵션을 선택하면 바로 총액이 바뀌어야 한다. 별도의 계산 버튼을 누르거나 페이지를 새로고침할 필요가 없어야 했다.
기본 금액은 600,000원으로 두고, 사용자가 선택한 옵션들의 가격을 더하는 방식으로 계산했다.
흐름은 다음과 같다.
기본 금액 600,000원 설정
-> Google Sheet에서 옵션 목록 불러오기
-> category별로 select 박스 생성
-> 사용자가 옵션 선택
-> 모든 select의 value 값을 순회
-> 선택된 금액 합산
-> 기본 금액과 합쳐 총액 표시
계산 결과는 사용자가 읽기 쉽게 천 단위 콤마를 붙여 보여줬다.
total.toLocaleString()
이 부분은 작지만 사용자 경험에서 중요했다.
1350000보다 1,350,000원이 훨씬 읽기 쉽기 때문이다.
category 기준으로 동적 렌더링하기
Google Sheet에서 가져온 데이터는 단순한 배열 형태다.
[
{ "category": "helper", "name": "1인 추가 (8시간)", "price": 110000 },
{ "category": "coffin", "name": "특관", "price": 200000 },
{ "category": "service", "name": "꽃관", "price": 150000 }
]
이 데이터를 화면에 그대로 뿌리면 보기 어렵기 때문에 category 기준으로 묶어서 보여주었다.
예를 들어 helper는 음식 서빙 도우미, coffin은 관, service는 서비스 옵션처럼 한글 제목으로 매핑했다.
helper -> 음식 서빙 도우미
clothes -> 수의
coffin -> 관
service -> 서비스
vehicle -> 장의 차량
이렇게 하면 시트에서는 개발자가 이해하기 쉬운 category 값을 사용하고, 화면에서는 사용자가 이해하기 쉬운 한글 제목으로 보여줄 수 있다.
또 나중에 옵션이 늘어나더라도 같은 category에 행만 추가하면 자동으로 select 박스 안에 반영된다.
운영자가 가격을 쉽게 수정하게 만든 구조
이 구조의 가장 큰 장점은 운영자가 직접 가격을 수정할 수 있다는 점이다.
예를 들어 리무진 가격이 바뀌면 개발자에게 요청하지 않고 Google Sheet에서 해당 행의 price 값만 수정하면 된다.
리무진 | 400000
이 값을 수정하면 사이트에서 다시 데이터를 불러올 때 변경된 가격이 반영된다.
즉, 단가 수정만으로는 재배포가 필요하지 않다.
물론 주의해야 할 점도 있다.
컬럼명은 변경하지 않는다
price에는 숫자만 입력한다
category 값은 정해진 값만 사용한다
시트 탭 이름을 임의로 바꾸지 않는다
민감정보는 시트에 넣지 않는다
이런 규칙만 지키면 운영자는 개발 지식 없이도 견적 옵션을 관리할 수 있다.
외주 프로젝트에서 이 부분은 꽤 중요했다. 사이트를 넘긴 뒤에도 모든 수정을 개발자가 해줘야 한다면 유지보수 부담이 커진다. 반대로 운영자가 직접 바꿀 수 있는 부분을 분리해두면, 개발자와 클라이언트 모두 부담이 줄어든다.
이번 기능을 만들며 느낀 점
이번 작업을 하면서 “무조건 DB를 쓰는 것이 정답은 아니다”라는 생각을 했다.
후기나 관리자 통계처럼 보안과 정합성이 필요한 데이터는 DB에 저장하는 것이 맞다. 하지만 견적 옵션처럼 운영자가 자주 수정하고, 민감하지 않은 데이터는 Google Sheet가 오히려 더 좋은 선택일 수 있다.
중요한 것은 데이터의 성격을 구분하는 것이었다.
자주 바뀌고 비개발자가 수정해야 하는 데이터인가?
민감정보가 포함되어 있는가?
사용자가 직접 입력하는 데이터인가?
검증과 권한 처리가 필요한가?
이 질문을 기준으로 보면 어떤 데이터는 DB에, 어떤 데이터는 Google Sheet에 두는 것이 더 자연스러웠다.
회고
처음에는 견적 옵션을 코드에 직접 넣는 것이 가장 빠른 방법처럼 보였다. 하지만 실제 운영까지 생각하면 빠르게 만드는 것보다 나중에 수정하기 쉬운 구조가 더 중요했다.
Google Sheet를 견적 옵션 DB처럼 사용한 것은 완벽한 데이터베이스 설계라기보다는, 이 프로젝트 상황에 맞춘 현실적인 선택이었다.
비개발자가 직접 수정할 수 있고, 개발자는 핵심 로직과 예외 처리에 집중할 수 있었다는 점에서 꽤 만족스러운 구조였다.
다음 프로젝트에서도 비슷한 상황이 있다면 먼저 데이터의 성격을 나눠볼 것 같다.
“이 데이터는 누가, 얼마나 자주, 어떤 방식으로 수정할 것인가?”를 먼저 물어보면 기술 선택이 훨씬 명확해진다는 걸 배웠다.
'개발 프로젝트 > [외주 프로젝트] 첫 외주 웹사이트 제작기: 기획부터 배포까지' 카테고리의 다른 글
| 관리자 페이지와 통계 기능 만들기 (0) | 2026.06.18 |
|---|---|
| 후기 기능 만들기: 계정 없이 등록하고 비밀번호로 삭제하기 (0) | 2026.06.16 |
| 왜 Next.js와 Spring Boot를 썼나 (0) | 2026.06.12 |
| 말로 들은 요청을 기능 명세로 바꾸는 과정 (0) | 2026.06.09 |
| 첫 외주 웹개발을 맡게 됐다: 프로젝트 첫인상과 시작 전 고민 (0) | 2026.06.06 |