아키텍쳐 흐름

지난 포스팅 [FastAPI] Azure Service Bus와 Function App을 활용한 데이터 전송 (1) 에서는 Service Bus에 JSON 파일을 전송하면 Azure Functoin App에서 트리거로 감지해 JSON 파일을 FastAPI Server 웹소켓으로 전송하고 해당 내용을 브로드캐스트로 모든 병원 프론트엔드로 전송하는 로직을 구현했습니다.
이번 포스팅에서는 구급대원 프론트엔드에서 들어온 음성 녹음 파일을 FastAPI Server에서 처리하고 그 내용을 Azure Service Bus로 전송함으로써 구급대원 프론트엔드부터 병원 프론트엔드까지 한 번에 가도록 완성을 해보겠습니다.
코드 구현
위에서 언급한 내용을 완성하기 위해 services 폴더에 event_publisher.py와 search_hospital.py 코드를 작업해야 합니다.
event_publisher.py
먼저 event_publisher.py 코드입니다.
FastAPI(생산자)가 생성한 데이터를 Azure Service Bus로 안전하게 던져주는 핵심 로직입니다.
# services/event_publisher.py
import json
import os
from azure.servicebus import ServiceBusClient, ServiceBusMessage
from dotenv import load_dotenv
load_dotenv()
# .env 또는 환경 변수에서 가져오기
SERVICEBUS_CONN_STR = os.getenv("SERVICEBUS_CONNECTION") # Azure Portal에서 복사한 그 문자열
TOPIC_NAME = "az-patient-event-hub"
def publish_patient_event(patient_data: dict, log_id: int):
"""
분석된 환자 데이터를 Azure Service Bus Topic으로 발행합니다.
"""
# 1. 클라이언트 생성
client = ServiceBusClient.from_connection_string(SERVICEBUS_CONN_STR)
with client:
# 2. 송신자(Sender) 생성
sender = client.get_topic_sender(topic_name=TOPIC_NAME)
with sender:
# 3. 메시지 생성
# body: 실제 GPT 분석 결과 JSON
# session_id: 환자별 순서 보장을 위해 log_id 또는 고유 ID 사용
# application_properties: Azure Function에서 필터링하거나 읽을 메타데이터
message = ServiceBusMessage(
body=json.dumps(patient_data, ensure_ascii=False),
session_id=f"patient-{log_id}",
application_properties={
"Region": "Seoul", # 실제 환경에선 동적으로 설정 가능
"PatientState": "NEW_EMERGENCY"
}
)
# 4. 발행!
sender.send_messages(message)
print(f"🚀 [ServiceBus] 메시지 발행 성공: patient-{log_id}")
ServiceBusClient, ServiceBusMessage 두 가지가 Azure Service Bus와 대화하기 위한 공식 라이브러리들입니다.
import는 라이브러리 전체를 from ~ import 는 해당 라이브러리에서 특정부분만 들고옵니다. 예를 들어 azure.servicebus 안에 ServiceBusClient, ServiceBusMessage 두 가지들고 옵니다.
import json
import os
from azure.servicebus import ServiceBusClient, ServiceBusMessage
from dotenv import load_dotenv
load_dotenv()
Azure Portal에서 가져온 "연결 문자열"과 데이터를 던질 구체적인 Topic 이름입니다.
SERVICEBUS_CONN_STR = os.getenv("SERVICEBUS_CONNECTION")
TOPIC_NAME = "az-patient-event-hub"
서비스 버스와의 전체 연결 통로를 엽니다. 아직 메시지를 보낸 건 아니고, 전화기를 들고 번호를 누른 상태와 같습니다.
앞에서 import한 ServiceBusClient와 그 인자로 연결 문자열을 줍니다.
def publish_patient_event(patient_data: dict, log_id: int):
client = ServiceBusClient.from_connection_string(SERVICEBUS_CONN_STR)
통신이 끝나면 자동으로 연결을 닫아주는 역할을 합니다.
여기서 사용한 client는 ServiceBusClient 클래스를 보고 만든 개체이고 sender는 특정 Topic(az-patient-event-hub)으로 메시지를 쏠 수 있는 전용 발사대입니다.
여기서 사용한 with는 파이썬의 '예약어(Keyword)' 또는 '구문(Statement)'입니다. 정확한 용어는 컨텍스트 매니저(Context Manager)라고 부릅니다.
with를 쓰면 코드가 끝나거나, 중간에 에러가 터져서 튕겨 나가더라도 파이썬이 알아서 연결을 안전하게 닫아줍니다.
with client:
sender = client.get_topic_sender(topic_name=TOPIC_NAME)
with sender:
다음으로는 ServiceBusMessage 클래스를 사용하여 message 객체를 만듭니다.
body는 실제 내용물입니다. 우리가 원하는 실제 정보가 담겨있습니다.
session_id는 순서 보장용 번호표입니다.
application_properties은 소포 겉면에 붙이는 포스트잇입니다. Azure Function이 내용을 다 열어보지 않고도 "어느 지역인지", "어떤 상태인지"를 바로 알 수 있게 해주는 메타데이터입니다.
message = ServiceBusMessage(
body=json.dumps(patient_data, ensure_ascii=False),
session_id=f"patient-{log_id}",
application_properties={
"Region": "Seoul",
"PatientState": "NEW_EMERGENCY"
}
)
search_hospital.py
먼저 search_hospital.py 코드입니다.
이 코드는 FastAPI 서버와 Oracle 데이터베이스 사이의 대화를 담당하는 핵심 로직입니다. 특히 특정 병원의 상세 정보를 주민등록번호(HPID)로 조회하는 기능을 수행합니다.
# services/search_hospital.py
import oracledb
from database import get_db_connection
def get_hospital_details_by_id(hpid: str):
"""
HPID를 조건으로 특정 병원의 이름, 좌표, 전화번호를 가져옵니다.
"""
connection = get_db_connection()
cursor = connection.cursor()
try:
# HPID로 특정 병원 1건 조회
sql = """
SELECT hospital_nm, x_pos, y_pos, telno, hpid
FROM HOSPITAL_MASTER
WHERE hpid = :hpid
"""
cursor.execute(sql, [hpid])
row = cursor.fetchone()
if row:
# 딕셔너리 형태로 변환
return {
"hospital_nm": row[0],
"x_pos": row[1],
"y_pos": row[2],
"telno": row[3],
"hpid": row[4]
}
return None
finally:
cursor.close()
connection.close()
Python에서 Oracle DB에 접속할 수 있게 해주는 라이브러리을 import 합니다.
import oracledb
from database import get_db_connection
DB와 연결하고 cursor 변수를 사용합니다.
def get_hospital_details_by_id(hpid: str):
connection = get_db_connection()
cursor = connection.cursor()
Oracle DB에 쿼리할 내용을 작성합니다.
:hpid는 바인드 변수로 SQL 문 안에 직접 값을 넣지 않고 cursor가 보내주는 값을 사용합니다.
sql = """
SELECT hospital_nm, x_pos, y_pos, telno, hpid
FROM HOSPITAL_MASTER
WHERE hpid = :hpid
"""
cursor.execute(sql, [hpid])
쿼리 결과를 fetchone 하여 가져오빈다.
row = cursor.fetchone()
가져온 값을 리턴해줍니다.
if row:
return {
"hospital_nm": row[0],
"x_pos": row[1],
"y_pos": row[2],
"telno": row[3],
"hpid": row[4]
}
여기서는 with 문을 쓰지 않았기 때문에 수동으로 close( ) 사용하여 닫아줍니다.
finally:
cursor.close()
connection.close()
여기까지 작성했다면 마지막으로 main.py에 라우터를 정의해주고 서버를 켜줍니다.
로컬 테스트
서버가 정상적으로 작동합니다.

다음으로 vscode에서 F5를 눌러 함수를 작동합니다.

이번에는 곧바로 postman으로 가서 열어준 포트인 http://0.0.0.0:8000/api/v1/audio 로 오디오 파일을 POST 합니다.
조금 후에 오디오 파일의 분석이 잘 나옴을 확인할 수 있습니다.

다음으로 Azure Function에서도 Service Bus 트리거를 작동하고 FastAPI 로 데이터를 잘 전송했음을 확인할 수 있습니다.

FastAPI 서버의 로그도 잘 찍혀있습니다.

기존의 로직이었던 DB에 데이터를 적재하는 과정도 잘 동작합니다.

마지막으로 Azure 포털에 접속해 Azure Service Bus에 찍힌 로그를 확인할 수 있습니다.

'FastAPI' 카테고리의 다른 글
| [FastAPI] JWT를 이용한 회원가입/로그인 서버 구현 (0) | 2025.12.21 |
|---|---|
| [FastAPI] Nginx 사용하기 (0) | 2025.12.18 |
| [FastAPI] Azure Service Bus와 Function App을 활용한 데이터 전송 (1) (0) | 2025.12.18 |
| [FastAPI] Naver API를 사용한 거리 및 최단경로 시간 탐색 (0) | 2025.12.17 |
| [FastAPI] websocket을 사용한 통신 (0) | 2025.12.17 |