251126 [ Day 96 ] - Project (17)

TaeHyun·2025년 11월 26일

TIL

목록 보기
113/184

시작하며

오늘은 DB 연결 및 종료 기능을 Service 파일로 분리를 하였다. 다른 기능들과 마찬가지로 분리만 하면 될 줄 알고 시작했는데, 작업을 하면서 생각해보니 연결을 하고 SQL 등 작업을 한 다음에 종료 처리를 해야 되는데 일반적인 함수로는 불가능하기에 여러 방법을 찾아보고 공부해봤다.

Context Manager

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)

Decorator

에러 처리 부분도 중복되는 부분이 너무 많아서 간단하게 처리할 방법이 없을까 찾아봤는데, 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

Result

@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 부분은 더 이상 수정할 게 없을 것 같은데, 이러다 항상 하나씩 보이는 게 생겼기 때문에 아직은 잘 모르겠다.

profile
Hello I'm TaeHyunAn, Currently Studying Data Analysis

0개의 댓글