오늘은 DB 연결 및 종료 기능을 Service 파일로 분리를 하였다. 다른 기능들과 마찬가지로 분리만 하면 될 줄 알고 시작했는데, 작업을 하면서 생각해보니 연결을 하고 SQL 등 작업을 한 다음에 종료 처리를 해야 되는데 일반적인 함수로는 불가능하기에 여러 방법을 찾아보고 공부해봤다.
Python 내장 기능 중 @contextmanager 데코레이터를 사용하면 DB 연결을 하고 yield와 with as를 통해 변수를 전달한 뒤, SQL문 작업 후 with가 끝이 나면 다시 yield 이후의 코드가 실행되는 구조로 코드를 구현할 수 있는 것으로 공부했다.
# DB 연결 함수
@contextmanager
def db_transaction():
"""
DB 연결 및 종료 처리
"""
# DB에서 데이터 조회
conn, cursor = None, None
try:
conn, cursor = get_connection()
if conn is None:
print("DB 연결 실패")
raise DBException("DB 연결 실패", 503)
# 함수 일시 정지(with as로 변수 전달)
yield conn, cursor
# 항상 커밋 실행
conn.commit()
# 에러 발생 시 내용 catch
except DBException:
raise
except Exception as e:
print(f"DB 오류 : {e}")
if conn:
conn.rollback() # 에러 발생 시 롤백
raise DBException(f"DB 오류 : {e}", 500)
finally:
close_connection(conn, cursor)
에러 처리 부분도 중복되는 부분이 너무 많아서 간단하게 처리할 방법이 없을까 찾아봤는데, Exception을 상속받은 클래스를 만들어서 기존 코드에 사용하던 에러 처리 구조를 동일하게 가져가고 데코레이터를 직접 만들어서 처리하면 7줄 정도씩을 줄일 수 있었다.
# 에러 처리 클래스 생성
class DBException(Exception):
def __init__(self, message, status_code):
self.message = message
self.status_code = status_code
super().__init__(self.message)
# 에러 처리 데코레이터 생성
def handle_errors(context): # 데코레이터 생성
def decorator(func): # 원본 함수를 wrapper에 전달
def wrapper(*args, **kwargs): # 실제 실행되는 함수
try:
return func(*args, **kwargs)
except DBException as e:
return False, e.message, e.status_code
except Exception as e:
print(f"{context} : 오류 - {e}")
traceback.print_exc()
return False, f"{context} : 서버 오류 발생", 500
return wrapper
return decorator
@handle_errors("릴레이 상태 저장")
def update_relay_status_in_db(data):
"""
릴레이 상태를 DB에 업데이트
"""
with db_transaction() as (_, cursor):
# 상태 저장할 빈 리스트 생성
relays_to_update = []
for relay_ch, ch_status in data.items():
# True -> "on", False -> "off"
status_string = "on" if ch_status else "off"
relays_to_update.append((status_string, relay_ch))
print(f"릴레이 상태 저장 : 업데이트할 데이터 - {relays_to_update}")
# DB UPDATE
sql = "UPDATE relay_status SET status = %s WHERE relay_name = %s"
cursor.executemany(sql, relays_to_update)
print("릴레이 상태 저장 : 저장 완료")
return True, None, 201
며칠간의 리팩토링 과정을 통해 중복된 코드를 줄이고 재사용하면서 상당히 많은 줄 수를 감소시켰다.
분리 전
- relay_service.py: 193줄
- sensor_service.py: 122줄
- energy_service.py: 126줄
- 총합: 441줄
분리 후
- relay_service.py: 125줄 (-68줄)
- sensor_service.py: 74줄 (-48줄)
- energy_service.py: 110줄 (-16줄)
- 총합: 309줄
결과
- 총 132줄 감소 (30% 감소)
리팩토링 이후 모든 엔드포인트를 테스트해보고 이상이 없는 것을 확인했다. 이제 정말로 API 부분은 더 이상 수정할 게 없을 것 같은데, 이러다 항상 하나씩 보이는 게 생겼기 때문에 아직은 잘 모르겠다.