Python 업무 로직을 FastAPI에서 재사용
FastAPI 서버에서 재사용하는 방법을 알아봅니다.
Python 프로그램에는 화면 코드와 데이터베이스 코드만 있는 것이 아닙니다. 실제 업무를 처리하기 위한 다양한 Python 함수가 포함되어 있습니다.
예를 들어 급여 계산 함수가 있다고 해보겠습니다.
def calculate_salary(
base_salary,
bonus
):
total = (
base_salary
+ bonus
)
return total
Python 함수 안에는 다음과 같은 여러 종류의 로직이 존재할 수 있습니다.
웹 프로그램으로 변경하면 구조는 다음과 같이 바뀝니다.
import tkinter as tk
from tkinter import messagebox
def save():
name = entry_name.get()
if name == "":
messagebox.showwarning(
"확인",
"이름을 입력하세요."
)
return
print(name)
| 코드 | 역할 | 웹 전환 후 |
|---|---|---|
| entry_name.get() | 화면 입력 | Vue |
| messagebox.showwarning() | 화면 메시지 | Vue |
| name == "" | 업무 검증 | Python / Service |
웹으로 변경할 때 화면 처리 부분은 Vue가 담당하고, 검증이나 계산 같은 업무 로직은 Python에서 계속 활용할 수 있습니다.
def validate_name(name):
if name.strip() == "":
return False
return True
name = entry_name.get()
if not validate_name(name):
messagebox.showwarning(
"확인",
"이름을 입력하세요."
)
if not validate_name(
employee.name
):
raise HTTPException(
status_code=400,
detail="이름을 입력하세요."
)
validate_name()은 Tkinter와 FastAPI 양쪽에서 사용할 수 있습니다.
이것이 화면과 업무 로직을 분리하는 중요한 이유입니다.
def validate_name(name):
if name.strip() == "":
return False
return True
@app.post("/employees")
def create_employee(
employee: EmployeeCreate
):
if not validate_name(
employee.name
):
raise HTTPException(
status_code=400,
detail="이름을 입력하세요."
)
return {
"message":
"정상입니다."
}
def calculate_salary(
base_salary,
bonus
):
return (
base_salary
+ bonus
)
Tkinter 프로그램에서도 일반 Python 함수처럼 사용할 수 있습니다.
base_salary = 3000000
bonus = 500000
total = calculate_salary(
base_salary,
bonus
)
print(total)
이 계산 함수는 FastAPI에서도 그대로 사용할 수 있습니다.
from pydantic import BaseModel
class SalaryRequest(
BaseModel
):
base_salary: int
bonus: int
def calculate_salary(
base_salary,
bonus
):
return (
base_salary
+ bonus
)
@app.post("/salary")
def salary(
data: SalaryRequest
):
total = calculate_salary(
data.base_salary,
data.bonus
)
return {
"total_salary":
total
}
{
"base_salary": 3000000,
"bonus": 500000
}
<script setup>
import { ref } from 'vue'
import axios from 'axios'
const baseSalary = ref(0)
const bonus = ref(0)
const totalSalary = ref(0)
async function calculate() {
const response =
await axios.post(
'http://localhost:8000/salary',
{
base_salary:
baseSalary.value,
bonus:
bonus.value
}
)
totalSalary.value =
response.data.total_salary
}
</script>
<template>
<div>
<h2>
급여 계산
</h2>
<p>
기본급
<input
type="number"
v-model.number="baseSalary"
>
</p>
<p>
상여금
<input
type="number"
v-model.number="bonus"
>
</p>
<button @click="calculate">
계산
</button>
<p>
총 급여 :
{{ totalSalary }}
</p>
</div>
</template>
업무 프로그램에서는 입력값 검증이 매우 중요합니다.
def validate_age(age):
if age <= 0:
return False
return True
if not validate_age(
employee.age
):
raise HTTPException(
status_code=400,
detail=
"나이는 0보다 커야 합니다."
)
def validate_phone(phone):
if phone == "":
return False
if not phone.startswith(
"010-"
):
return False
return True
if not validate_phone(
employee.phone
):
raise HTTPException(
status_code=400,
detail=
"전화번호 형식이 올바르지 않습니다."
)
이처럼 기존 Python의 검증 함수를 FastAPI에서도 활용할 수 있습니다.
if (
employee.name.trim() === ''
) {
alert(
'이름을 입력하세요.'
)
return
}
| 위치 | 목적 |
|---|---|
| Vue 검증 | 사용자 편의 |
| FastAPI 검증 | 데이터 보호 |
def normalize_name(name):
return name.strip()
name = normalize_name(
employee.name
)
def validate_name(name):
return (
name.strip() != ""
)
def validate_phone(phone):
return phone.startswith(
"010-"
)
def normalize_name(name):
return name.strip()
@app.post("/employees")
def create_employee(
employee: EmployeeCreate
):
if not validate_name(
employee.name
):
raise HTTPException(
status_code=400,
detail=
"이름을 입력하세요."
)
if not validate_phone(
employee.phone
):
raise HTTPException(
status_code=400,
detail=
"전화번호 형식이 잘못되었습니다."
)
name = normalize_name(
employee.name
)
# DB 저장
return {
"name": name,
"message":
"등록되었습니다."
}
프로그램이 커지면 main.py에 모든 함수를 넣지 않는 것이 좋습니다.
검증, 계산, 조건 판단, 데이터 가공 등을 담당합니다.
SELECT, INSERT, UPDATE, DELETE 등을 담당합니다.
def validate_name(name):
return (
name.strip() != ""
)
def validate_phone(phone):
return phone.startswith(
"010-"
)
def normalize_name(name):
return name.strip()
FastAPI에서는 필요한 업무 함수를 import합니다.
from services.employee_service import (
validate_name,
validate_phone,
normalize_name
)
Service는 프로그램의 업무 규칙을 처리합니다.
| 계층 | 역할 |
|---|---|
| Vue | 화면 |
| FastAPI Router | HTTP 요청 |
| Service | 업무 처리 |
| Repository | DB 처리 |
| Database | 데이터 저장 |
- DB에 저장
- DB에서 조회
- DB 수정
- DB 삭제
- 검증
- 계산
- 조건 판단
- 업무 규칙
- 데이터 가공
def calculate_discount(
price,
member_grade
):
if member_grade == "VIP":
return price * 0.8
if member_grade == "GOLD":
return price * 0.9
return price
이 함수는 DB나 Tkinter 화면과 관계가 없습니다.
따라서 services/ 폴더로 그대로 이동할 수 있습니다.
def calculate_discount(
price,
member_grade
):
if member_grade == "VIP":
return price * 0.8
if member_grade == "GOLD":
return price * 0.9
return price
class DiscountRequest(
BaseModel
):
price: int
member_grade: str
@app.post("/discount")
def discount(
data: DiscountRequest
):
result = (
calculate_discount(
data.price,
data.member_grade
)
)
return {
"result": result
}
Vue는 필요한 값을 서버에 보내고 계산 결과만 받아 화면에 표시하면 됩니다.
from datetime import datetime
def get_year():
now = datetime.now()
return now.year
@app.get("/current-year")
def current_year():
return {
"year":
get_year()
}
기존 Python의 날짜 처리 기능 역시 서버에서 그대로 활용할 수 있습니다.
def read_text_file(
filename
):
with open(
filename,
"r",
encoding="utf-8"
) as file:
return file.read()
@app.get("/notice")
def get_notice():
content = (
read_text_file(
"notice.txt"
)
)
return {
"content":
content
}
Vue는 서버가 전달한 파일 내용을 받아 화면에 출력합니다.
- DB 처리
- 파일 처리
- 복잡한 계산
- 업무 규칙
- 중요한 검증
- 보안 관련 처리
- 외부 API 호출
- 서버 파일 접근
- 화면 표시
- 사용자 입력
- 간단한 검증
- 버튼 이벤트
- 화면 이동
- API 호출
단순한 가격 계산이라면 Vue에서도 처리할 수 있습니다.
total.value = price.value * count.value
하지만 업무 규칙이 복잡해진다면 FastAPI에서 처리하는 것이 좋습니다.
같은 계산 규칙을 여러 화면에서 사용한다고 가정해 봅니다.
각 화면마다 코드를 작성하면 같은 업무 로직이 반복될 수 있습니다.
기존 Python 사원 등록 코드가 다음과 같다고 가정해 봅니다.
def save_employee():
name = entry_name.get()
phone = entry_phone.get()
if name == "":
messagebox.showwarning(
"확인",
"이름을 입력하세요."
)
return
conn = sqlite3.connect(
"employee.db"
)
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO employee
(name, phone)
VALUES (?, ?)
""",
(
name,
phone
)
)
conn.commit()
conn.close()
| 기능 | 종류 | 웹 전환 |
|---|---|---|
| Entry 값 읽기 | 화면 | Vue |
| 이름 검증 | 업무 로직 | Service |
| INSERT | DB 처리 | Repository |
<script setup>
import { reactive } from 'vue'
import axios from 'axios'
const employee = reactive({
name: '',
phone: ''
})
async function save() {
try {
await axios.post(
'http://localhost:8000/employees',
employee
)
alert(
'등록되었습니다.'
)
} catch (error) {
alert(
error.response.data.detail
)
}
}
</script>
<template>
<input
v-model="employee.name"
>
<input
v-model="employee.phone"
>
<button @click="save">
등록
</button>
</template>
def validate_name(name):
if name.strip() == "":
return False
return True
import sqlite3
def insert_employee(
name,
phone
):
conn = sqlite3.connect(
"employee.db"
)
cursor = conn.cursor()
cursor.execute(
"""
INSERT INTO employee
(name, phone)
VALUES (?, ?)
""",
(
name,
phone
)
)
conn.commit()
conn.close()
from fastapi import (
APIRouter,
HTTPException
)
from pydantic import BaseModel
from services.employee_service import (
validate_name
)
from repositories.employee_repository import (
insert_employee
)
router = APIRouter()
class EmployeeCreate(
BaseModel
):
name: str
phone: str
@router.post("/employees")
def create_employee(
employee: EmployeeCreate
):
if not validate_name(
employee.name
):
raise HTTPException(
status_code=400,
detail=
"이름을 입력하세요."
)
insert_employee(
employee.name,
employee.phone
)
return {
"message":
"등록되었습니다."
}
기존에는 하나의 함수가 화면, 검증, DB 저장을 모두 담당했지만 웹 프로그램에서는 각 역할이 분리됩니다.
프로그램이 작을 때는 하나의 함수가 편할 수 있습니다. 하지만 웹 프로그램이 커질수록 역할을 분리하는 것이 관리하기 쉽습니다.
다음 코드는 Tkinter 화면에 직접 종속되어 있습니다.
entry_name.get() tree.insert(...) messagebox.showinfo(...) window.destroy()
이러한 코드는 Vue 방식으로 다시 만들어야 합니다.
calculate_salary() validate_phone() calculate_discount() read_file() insert_employee() find_employee()
| 재사용하기 좋은 코드 | 재사용하기 어려운 코드 |
|---|---|
| 계산 함수 | Tkinter Entry |
| 검증 함수 | Tkinter Button |
| DB 함수 | Tkinter Treeview |
| 파일 함수 | Tkinter Frame |
| 데이터 변환 함수 | messagebox |
| 문자열 처리 | 화면 위치 지정 |
| 날짜 처리 | - |
| 외부 API 함수 | - |
| 업무 규칙 | - |
처음부터 화면, 업무 로직, DB 코드를 분리하면 나중에 웹으로 변경하기가 훨씬 쉬워집니다.
잘 분리된 프로그램이라면 Service와 Repository 같은 아래쪽 Python 로직은 상당 부분 그대로 유지할 수 있습니다.
- Python 프로그램에는 화면과 DB뿐 아니라 계산·검증·문자열·날짜·파일 처리 등의 업무 로직이 존재합니다.
- Tkinter 화면에 직접 의존하지 않는 Python 함수는 FastAPI에서도 상당 부분 그대로 재사용할 수 있습니다.
- Vue는 화면을 담당하고 FastAPI는 서버 요청 처리를 담당합니다.
- 이름 검증, 전화번호 검증, 급여 계산, 할인 계산 같은 로직은 Service에 배치할 수 있습니다.
- SELECT, INSERT, UPDATE, DELETE와 같은 DB 처리는 Repository에 배치할 수 있습니다.
- 권장되는 전체 구조는 Vue → Router → Service → Repository → Database입니다.
- Vue의 검증은 사용자 편의를 위한 1차 검증, FastAPI의 검증은 데이터 보호를 위한 2차 검증으로 활용할 수 있습니다.
- 업무 규칙을 Service에 모으면 여러 Vue 화면에서 동일한 규칙을 공유하기 쉽습니다.
-
entry_name.get(),messagebox.showinfo()처럼 Tkinter에 종속된 코드는 Vue 방식으로 다시 만들어야 합니다. - 반대로 계산, 검증, DB, 파일, 문자열, 날짜 처리처럼 화면과 독립된 Python 코드일수록 재사용하기 쉽습니다.
- 처음부터 UI, Service, Repository를 분리해 두면 데스크톱 프로그램을 웹으로 전환하기가 훨씬 쉬워집니다.
- 잘 분리된 프로그램에서 웹 전환 시 가장 크게 바뀌는 부분은 Tkinter → Vue + FastAPI이며, 아래쪽 Python 업무 로직은 상당 부분 유지할 수 있습니다.