파이썬을 스크립트에서 웹 서비스 플랫폼으로 격상시키다
30단계까지 우리는 '한 거래처'의 데이터를 처리하는 파이썬 코드를 짰습니다.
이제 31단계부터는 관점을 완전히 바꿉니다. 수백 개의 회계사무소와 수천 개의 거래처가 동시에 웹에 접속해 각자의 데이터를 업로드하고 AI 결산을 돌리는 Multi-Tenant B2B SaaS 플랫폼을 아키텍처 레벨에서 설계합니다.
왜 SaaS (Software as a Service) 구조가 필요한가?
지금까지 짠 파이썬 코드는 엑셀 파일을 주면 내 컴퓨터에서 10초간 빙글빙글 돌다 결과를 내뱉는 "로컬 스크립트"입니다. 하지만 실제 비즈니스는 다릅니다.
이처럼 하나의 거대한 웹 서버가 수십, 수백 개의 조직 데이터를 안전하게 분리 보관하면서도 동일한 엔진(25~30단계의 파이썬 코드)을 서비스하는 구조를 SaaS라고 합니다.
Multi-Tenant: 절대 섞이면 안 되는 데이터의 격리
Tenant(테넌트)란 '입주자'라는 뜻입니다. 클라우드 빌딩(우리 시스템)에 A회계사무소와 B회계사무소가 방을 빌려 씁니다. 가장 중요한 것은 A사무소가 B사무소의 장부를 절대 들여다볼 수 없어야 한다는 점입니다.
/* 가장 중요한 DB 설계 철칙 */ CREATE TABLE customers ( id BIGINT PRIMARY KEY AUTO_INCREMENT, office_id BIGINT NOT NULL, -- [핵심] 이 거래처가 어느 회계사무소 소속인가? company_name VARCHAR(200) ); -- 파이썬/SQL의 모든 WHERE 쿼리문에는 무조건 AND office_id = ? 가 붙어야 합니다. SELECT * FROM transactions WHERE office_id = 1 AND customer_id = 55;
JWT 인증(Authentication)과 RBAC 권한(Authorization)
사용자가 로그인하면 시스템은 통행증(JWT: JSON Web Token)을 발급합니다. 이 통행증에는 "당신은 A사무소의 평직원"이라는 정보가 들어있습니다.
인증(Authentication): "너 누구야?" ➡️ "A사무소 직원입니다. (비밀번호 통과)"
권한(Authorization): "너 뭐 할 수 있어?" ➡️ "자료 열람은 되지만, [분개 승인] 권한은 없습니다." (RBAC - Role Based Access Control)
ROLE_PERMISSIONS = { "ACCOUNTANT": ["customer.read", "transaction.read", "journal.approve"], "STAFF": ["customer.read", "transaction.read"], # 승인 불가 "CLIENT": ["own_company.read", "document.upload"] }
FastAPI: 파이썬 코드를 웹 API로 변신시키다
사용자의 브라우저나 앱은 파이썬 함수를 직접 실행할 수 없습니다. 대신 GET /customers 같은 형태의 HTTP 요청을 보냅니다. 이를 파이썬 함수와 연결해 주는 가장 빠르고 현대적인 프레임워크가 바로 FastAPI입니다.
from fastapi import FastAPI, Depends app = FastAPI() @app.get("/offices/{office_id}/customers") def get_customers(office_id: int): # (보안) 현재 로그인한 토큰의 사용자가 해당 office_id 소속인지 검증! customers = db.query(Customer).filter(Customer.office_id == office_id).all() return customers
로딩창 방지 기술: 비동기 처리와 Job Queue
거래처가 1년치 세금계산서 PDF 500장을 업로드했습니다. 이걸 서버가 OCR로 다 읽을 때까지 사용자 브라우저를 모래시계(로딩) 상태로 5분간 멈춰둘 것인가요?
그렇지 않습니다. "파일 잘 받았습니다"라고 1초 만에 응답한 뒤, 뒤편(Background)의 Worker(일꾼)에게 큐(Queue)로 작업을 던지는 아키텍처가 필수입니다.
작업 상태는 PENDING ➡️ PROCESSING (progress: 60%) ➡️ COMPLETED 또는 FAILED 로 갱신됩니다.
법적 책임의 방패: 감사 로그 (Audit Log)
누군가 AI가 추천한 `소모품비`를 `비품`으로 수정했다면, 누가(user_id), 언제(created_at), 무엇을(old_value ➡️ new_value), 어떤 IP 주소에서 바꿨는지 반드시 기록해야 합니다.
/* Audit Log 테이블 구조 */ CREATE TABLE audit_logs ( user_id BIGINT, action VARCHAR(100), -- 'JOURNAL_UPDATE' target_id BIGINT, -- 변경된 전표 번호 old_value JSON, -- {"account": "소모품비"} new_value JSON, -- {"account": "비품"} created_at DATETIME );
Service Layer 패턴과 디렉터리 분리
개발 초보자가 흔히 하는 실수(Spaghetti Code)는 API를 받는 함수 안에 DB 쿼리, 계산, AI 연동을 전부 다 집어넣는 것입니다. 상용 플랫폼에서는 이를 레이어(Layer) 단위로 엄격히 분리해야 합니다.
Controller(API): @app.post ➡️ "요청을 받고 응답만 줍니다."
Service: JournalService.create() ➡️ "실제 복잡한 비즈니스 계산(Rule 엔진, AI 호출)을 합니다."
Repository(DB): "DB에 읽고 쓰는 쿼리만 담당합니다."
32단계 예고: 맥락을 기억하는 AI 회계 비서 봇
31단계에서 우리는 그동안 만든 코드를 완벽한 Multi-Tenant 웹 시스템으로 감싸 올렸습니다.
이제 마지막 조각이 남았습니다. 이렇게 구축된 수많은 거래/결산 DB에 접근하여 "이번 달 대한제조에서 부가세 신고 전 누락된 증빙이 있어?"라고 챗봇으로 물어보면, 데이터베이스를 검색해 대답해 주는 대화형 AI 회계비서(Agent)를 32단계에서 장착해 보겠습니다.