Python 업무 로직을 FastAPI에서 재사용

PYTHON BUSINESS LOGIC
Chapter 13. Python 업무 로직을 FastAPI에서 재사용
기존 Python 프로그램의 계산·검증·문자열·날짜·파일 처리 로직을
FastAPI 서버에서 재사용하는 방법을 알아봅니다.
Python 업무 로직과 FastAPI 개발
화면과 독립된 Python 코드는 웹 프로그램으로 전환해도 상당 부분 그대로 활용할 수 있습니다.
13.1 기존 Python 업무 로직이란?

Python 프로그램에는 화면 코드와 데이터베이스 코드만 있는 것이 아닙니다. 실제 업무를 처리하기 위한 다양한 Python 함수가 포함되어 있습니다.

🧮
계산
검증
🔤
문자열 처리
📅
날짜 처리
📁
파일 처리
⚙️
데이터 가공

예를 들어 급여 계산 함수가 있다고 해보겠습니다.

Python
def calculate_salary(
    base_salary,
    bonus
):
    total = (
        base_salary
        + bonus
    )

    return total
💡 재사용할 수 있는 이유
이 함수는 Tkinter의 Entry, Button, Window와 아무 관계가 없습니다. 따라서 웹 프로그램으로 변경해도 Python 서버에서 그대로 활용할 수 있습니다.
13.2 기존 Python 프로그램 구조
Tkinter → Python 함수 → SQLite

Python 함수 안에는 다음과 같은 여러 종류의 로직이 존재할 수 있습니다.

Python 함수 ├─ 입력값 검증 ├─ 금액 계산 ├─ 날짜 계산 ├─ 데이터 가공 ├─ 파일 처리 └─ DB 처리

웹 프로그램으로 변경하면 구조는 다음과 같이 바뀝니다.

Vue → REST API → FastAPI → Python 업무 로직 → DB / 파일 / 외부 기능
핵심 포인트
Vue로 화면이 변경되더라도 화면과 독립된 Python 업무 로직은 FastAPI 서버 쪽에서 계속 사용할 수 있습니다.
13.3 화면 코드와 업무 로직을 구분하기
Tkinter
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에서 계속 활용할 수 있습니다.

13.4 업무 로직 분리하기
재사용 가능한 검증 함수
def validate_name(name):

    if name.strip() == "":
        return False

    return True
Tkinter에서 사용
Tkinter
name = entry_name.get()

if not validate_name(name):

    messagebox.showwarning(
        "확인",
        "이름을 입력하세요."
    )
FastAPI에서 사용
FastAPI
if not validate_name(
    employee.name
):
    raise HTTPException(
        status_code=400,
        detail="이름을 입력하세요."
    )
💡 같은 함수를 다른 화면에서 사용
validate_name()은 Tkinter와 FastAPI 양쪽에서 사용할 수 있습니다. 이것이 화면과 업무 로직을 분리하는 중요한 이유입니다.
13.5 Python 함수 재사용의 기본 구조
기존 함수
def validate_name(name):

    if name.strip() == "":
        return False

    return True
FastAPI
@app.post("/employees")
def create_employee(
    employee: EmployeeCreate
):

    if not validate_name(
        employee.name
    ):
        raise HTTPException(
            status_code=400,
            detail="이름을 입력하세요."
        )

    return {
        "message":
            "정상입니다."
    }
Vue ↓ POST /employees ↓ FastAPI ↓ validate_name() ↓ 검증 결과 ↓ FastAPI 응답 ↓ Vue
13.6 계산 로직 재사용하기
급여 계산 함수
def calculate_salary(
    base_salary,
    bonus
):
    return (
        base_salary
        + bonus
    )

Tkinter 프로그램에서도 일반 Python 함수처럼 사용할 수 있습니다.

Python
base_salary = 3000000
bonus = 500000

total = calculate_salary(
    base_salary,
    bonus
)

print(total)
실행 결과
3500000

이 계산 함수는 FastAPI에서도 그대로 사용할 수 있습니다.

13.7 급여 계산 API 만들기
Pydantic 모델
from pydantic import BaseModel


class SalaryRequest(
    BaseModel
):
    base_salary: int
    bonus: int
기존 계산 함수
def calculate_salary(
    base_salary,
    bonus
):
    return (
        base_salary
        + bonus
    )
FastAPI
@app.post("/salary")
def salary(
    data: SalaryRequest
):

    total = calculate_salary(
        data.base_salary,
        data.bonus
    )

    return {
        "total_salary":
            total
    }
Vue가 보내는 데이터
JSON
{
  "base_salary": 3000000,
  "bonus": 500000
}
FastAPI 응답
{ "total_salary": 3500000 }
13.8 계산 처리 흐름
Vue 기본급 입력 / 상여금 입력 ↓ POST /salary ↓ FastAPI ↓ calculate_salary() ↓ Python 계산 ↓ 결과 반환 ↓ Vue 화면 출력
⭐ 중요한 점
기존 Python으로 작성한 계산 로직을 JavaScript로 다시 작성할 필요는 없습니다. FastAPI가 기존 Python 계산 함수를 호출하도록 만들 수 있습니다.
13.9 Vue에서 계산 API 호출하기
Vue
<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>
13.10 입력값 검증 로직

업무 프로그램에서는 입력값 검증이 매우 중요합니다.

검증 예
이름 필수 · 전화번호 형식 · 급여 음수 불가 · 나이는 0보다 커야 함 · 지정된 부서만 허용
기존 Python 검증 함수
def validate_age(age):

    if age <= 0:
        return False

    return True
FastAPI에서 재사용
if not validate_age(
    employee.age
):
    raise HTTPException(
        status_code=400,
        detail=
        "나이는 0보다 커야 합니다."
    )
13.11 전화번호 검증 함수
Python
def validate_phone(phone):

    if phone == "":
        return False

    if not phone.startswith(
        "010-"
    ):
        return False

    return True
FastAPI
if not validate_phone(
    employee.phone
):
    raise HTTPException(
        status_code=400,
        detail=
        "전화번호 형식이 올바르지 않습니다."
    )

이처럼 기존 Python의 검증 함수를 FastAPI에서도 활용할 수 있습니다.

13.12 Vue 검증과 Python 검증
Vue에서도 검증할 수 있습니다
Vue
if (
  employee.name.trim() === ''
) {
  alert(
    '이름을 입력하세요.'
  )

  return
}
위치 목적
Vue 검증 사용자 편의
FastAPI 검증 데이터 보호
Vue 1차 검증 → FastAPI 2차 검증 → DB 저장
13.13 Python 문자열 처리 재사용
Python
def normalize_name(name):

    return name.strip()
FastAPI에서 사용
name = normalize_name(
    employee.name
)
" 홍길동 " → normalize_name() → "홍길동"
13.14 여러 업무 로직을 조합하기
사원 등록 전 처리
① 이름 검증 → ② 전화번호 검증 → ③ 이름 공백 제거 → ④ DB 저장
업무 함수
def validate_name(name):
    return (
        name.strip() != ""
    )


def validate_phone(phone):
    return phone.startswith(
        "010-"
    )


def normalize_name(name):
    return name.strip()
FastAPI
@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":
            "등록되었습니다."
    }
13.15 업무 처리 함수를 별도 파일로 분리

프로그램이 커지면 main.py에 모든 함수를 넣지 않는 것이 좋습니다.

backend │ ├─ main.py │ ├─ services │ └─ employee_service.py │ └─ repositories └─ employee_repository.py
Service
업무 로직을 처리합니다.
검증, 계산, 조건 판단, 데이터 가공 등을 담당합니다.
Repository
데이터베이스를 처리합니다.
SELECT, INSERT, UPDATE, DELETE 등을 담당합니다.
13.16 employee_service.py
services/employee_service.py
def validate_name(name):

    return (
        name.strip() != ""
    )


def validate_phone(phone):

    return phone.startswith(
        "010-"
    )


def normalize_name(name):

    return name.strip()

FastAPI에서는 필요한 업무 함수를 import합니다.

Python
from services.employee_service import (
    validate_name,
    validate_phone,
    normalize_name
)
13.17 Service의 역할

Service는 프로그램의 업무 규칙을 처리합니다.

👤
등록 가능 여부
💰
급여 계산
🏷️
할인 계산
📦
재고 검사
📅
날짜 계산
입력값 검증
Vue
Router
Service
Repository
Database
계층 역할
Vue화면
FastAPI RouterHTTP 요청
Service업무 처리
RepositoryDB 처리
Database데이터 저장
13.18 Repository와 Service의 차이
Repository
  • DB에 저장
  • DB에서 조회
  • DB 수정
  • DB 삭제
Service
  • 검증
  • 계산
  • 조건 판단
  • 업무 규칙
  • 데이터 가공
Router → Service → 이름 검증 / 전화번호 검증 → Repository → INSERT
13.19 기존 Python 함수를 Service로 이동하기
기존 Python 함수
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/ 폴더로 그대로 이동할 수 있습니다.

services/order_service.py
def calculate_discount(
    price,
    member_grade
):

    if member_grade == "VIP":
        return price * 0.8

    if member_grade == "GOLD":
        return price * 0.9

    return price
서비스와 API 계층을 분리하는 웹 개발
업무 로직을 Service에 모아두면 여러 Vue 화면에서 같은 규칙을 재사용하기 쉬워집니다.
13.20 할인 계산 API
Pydantic 모델
class DiscountRequest(
    BaseModel
):
    price: int
    member_grade: str
FastAPI
@app.post("/discount")
def discount(
    data: DiscountRequest
):

    result = (
        calculate_discount(
            data.price,
            data.member_grade
        )
    )

    return {
        "result": result
    }

Vue는 필요한 값을 서버에 보내고 계산 결과만 받아 화면에 표시하면 됩니다.

13.21 날짜 처리 함수 재사용
Python
from datetime import datetime


def get_year():

    now = datetime.now()

    return now.year
FastAPI
@app.get("/current-year")
def current_year():

    return {
        "year":
            get_year()
    }

기존 Python의 날짜 처리 기능 역시 서버에서 그대로 활용할 수 있습니다.

13.22 파일 처리 함수 재사용
기존 Python 파일 함수
def read_text_file(
    filename
):

    with open(
        filename,
        "r",
        encoding="utf-8"
    ) as file:

        return file.read()
FastAPI
@app.get("/notice")
def get_notice():

    content = (
        read_text_file(
            "notice.txt"
        )
    )

    return {
        "content":
            content
    }

Vue는 서버가 전달한 파일 내용을 받아 화면에 출력합니다.

13.23 서버에서 처리해야 하는 기능
FastAPI 서버
  • DB 처리
  • 파일 처리
  • 복잡한 계산
  • 업무 규칙
  • 중요한 검증
  • 보안 관련 처리
  • 외부 API 호출
  • 서버 파일 접근
Vue
  • 화면 표시
  • 사용자 입력
  • 간단한 검증
  • 버튼 이벤트
  • 화면 이동
  • API 호출
13.24 Vue와 FastAPI 역할 분리

단순한 가격 계산이라면 Vue에서도 처리할 수 있습니다.

Vue
total.value =
  price.value
  * count.value

하지만 업무 규칙이 복잡해진다면 FastAPI에서 처리하는 것이 좋습니다.

상품 가격 + 회원 할인 + 쿠폰 할인 + 배송비 + 세금 + 이벤트 할인
💡 업무 규칙을 서버에 모으기
복잡한 계산을 서버에서 처리하면 여러 화면에서 같은 규칙을 공통으로 사용할 수 있습니다.
13.25 업무 로직을 서버에 두는 이유

같은 계산 규칙을 여러 화면에서 사용한다고 가정해 봅니다.

Vue 화면 A → 계산 코드 Vue 화면 B → 계산 코드 Vue 화면 C → 계산 코드

각 화면마다 코드를 작성하면 같은 업무 로직이 반복될 수 있습니다.

Vue A ─┐ Vue B ─┼→ FastAPI → Service Vue C ─┘
Service의 장점
업무 규칙을 한 곳에 작성하면 수정해야 할 때 Service만 변경하면 됩니다. 여러 화면에서 동일한 규칙을 공유하기도 쉬워집니다.
13.26 기존 Python 코드 변환 기준
기존 Python 코드 │ ├─ Tkinter 코드 │ └─ Vue │ ├─ DB 코드 │ └─ Repository │ ├─ 업무 로직 │ └─ Service │ └─ 프로그램 실행 코드 └─ FastAPI
⭐ 웹 전환의 핵심 분류
기존 코드를 무조건 다시 작성하는 것이 아니라, 코드의 역할을 먼저 구분한 뒤 알맞은 계층으로 이동시키는 것이 중요합니다.
13.27 변환 예제

기존 Python 사원 등록 코드가 다음과 같다고 가정해 봅니다.

기존 Tkinter + Python + SQLite
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
13.28 Vue 화면
Vue
<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>
Vue의 역할
Vue는 화면 표시, 사용자 입력, 버튼 이벤트, API 호출을 담당합니다.
13.29 Service
employee_service.py
def validate_name(name):

    if name.strip() == "":
        return False

    return True
Service의 역할
이름을 입력했는지 확인하는 것처럼 프로그램의 업무 규칙을 담당합니다.
13.30 Repository
employee_repository.py
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()
Repository의 역할
Repository는 업무 화면이나 HTTP 요청을 처리하지 않고 DB 처리에 집중합니다.
13.31 FastAPI Router
FastAPI Router
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":
            "등록되었습니다."
    }
13.32 분리된 전체 구조
Vue
Router
Service
Repository
SQLite
Vue ↓ POST /employees FastAPI Router ↓ Service ↓ 검증 Repository ↓ INSERT SQLite

기존에는 하나의 함수가 화면, 검증, DB 저장을 모두 담당했지만 웹 프로그램에서는 각 역할이 분리됩니다.

13.33 기존 프로그램과 비교
기존
save_employee() = 화면 읽기 + 검증 + DB 저장
Vue → 화면 Service → 검증 Repository → DB Router → 연결

프로그램이 작을 때는 하나의 함수가 편할 수 있습니다. 하지만 웹 프로그램이 커질수록 역할을 분리하는 것이 관리하기 쉽습니다.

13.34 Python 코드를 모두 재사용할 수 있는 것은 아니다

다음 코드는 Tkinter 화면에 직접 종속되어 있습니다.

재사용하기 어려운 코드
entry_name.get()

tree.insert(...)

messagebox.showinfo(...)

window.destroy()

이러한 코드는 Vue 방식으로 다시 만들어야 합니다.

재사용하기 쉬운 코드
calculate_salary()

validate_phone()

calculate_discount()

read_file()

insert_employee()

find_employee()
판단 기준
화면과 독립된 Python 코드일수록 재사용하기 쉽습니다.
13.35 재사용하기 좋은 Python 코드
재사용하기 좋은 코드 재사용하기 어려운 코드
계산 함수Tkinter Entry
검증 함수Tkinter Button
DB 함수Tkinter Treeview
파일 함수Tkinter Frame
데이터 변환 함수messagebox
문자열 처리화면 위치 지정
날짜 처리-
외부 API 함수-
업무 규칙-
13.36 좋은 Python 프로그램 구조

처음부터 화면, 업무 로직, DB 코드를 분리하면 나중에 웹으로 변경하기가 훨씬 쉬워집니다.

Python 프로그램 │ ├─ ui │ └─ 화면 │ ├─ services │ └─ 업무 로직 │ ├─ repositories │ └─ DB │ └─ models └─ 데이터 구조
Tkinter 프로그램
Tkinter → Service → Repository → DB
웹 프로그램
Vue → FastAPI → Service → Repository → DB
💡 잘 분리된 프로그램의 장점
웹으로 전환할 때 Service와 Repository를 상당 부분 그대로 활용할 수 있습니다.
13.37 데스크톱에서 웹으로 전환
기존 데스크톱 구조
┌────────────────────┐ │ Tkinter │ └─────────┬──────────┘ │ ▼ ┌────────────────────┐ │ Service │ └─────────┬──────────┘ │ ▼ ┌────────────────────┐ │ Repository │ └─────────┬──────────┘ │ ▼ ┌────────────────────┐ │ SQLite │ └────────────────────┘
웹 프로그램 구조
┌────────────────────┐ │ Vue │ └─────────┬──────────┘ │ REST API ▼ ┌────────────────────┐ │ FastAPI │ └─────────┬──────────┘ │ ▼ ┌────────────────────┐ │ Service │ └─────────┬──────────┘ │ ▼ ┌────────────────────┐ │ Repository │ └─────────┬──────────┘ │ ▼ ┌────────────────────┐ │ SQLite │ └────────────────────┘
⭐ 실제로 크게 바뀌는 부분
Tkinter → Vue + FastAPI

잘 분리된 프로그램이라면 Service와 Repository 같은 아래쪽 Python 로직은 상당 부분 그대로 유지할 수 있습니다.

📌 Chapter 13 핵심 정리
  • 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 업무 로직은 상당 부분 유지할 수 있습니다.

프로그램·홈페이지·강의가 필요하신가요?

프로그램 판매, 무료 다운로드, 홈페이지 제작, IT 강의 상담을 도와드립니다.

상담 신청하기