API 인터페이스는 어떻게 설계해야 합니까?
먼저 API의 인터페이스 레이어는 사용자가 사용하는 부분(Front)을 의미합니다.
즉, 사용자 관점에서 API를 의미합니다.
사용자는 API를 통해 데이터를 요청할 수 있습니다.
이 시점에서 사용자는 요청할 데이터의 수와 정렬 방법을 결정할 수 있습니다.
샘플 데이터는 아래와 같습니다.
과일이름 | 갯수 | 들어온 날짜 | 가격
------|------|----------|------
바나나 | 12 | 03.12 | 1200
토마토 | 23 | 03.13 | 2200
오랜지 | 21 | 03.13 | 3200
사용자는 2,000원 이상의 바나나 또는 과일 이름에 대한 정보만 얻을 수 있습니다.
그렇다면 API를 통해 요청해야 하며 두 가지 방법이 있습니다.
시나리오: 2000원 이상 과일의 이름과 가격 정보를 가져옵니다.
- 공통 정보를 하나의 경로로 결합
예) /the_fruits - 사용자는 API를 통해 조건에 따라 쿼리를 보냅니다.
예) /fruit?minPrice=2000&data=name&data=price
방법 1의 경우 인터페이스가 단순화됩니다.
매직 버튼을 생성하여 사용자가 버튼을 누르기만 하면 됩니다.
반면 방법 2의 경우 인터페이스 동작이 조금 복잡해진다.
인터페이스 제공자의 규칙에 따라 사용해야 합니다.
방법 1과 같은 인터페이스는 더 이상 업데이트가 필요하지 않은 제품용입니다.
특히 전자레인지, 계산기 등 해당 버튼을 누르면 의도한 기능을 명확하게 제공하는 제품에 사용됩니다.
이에 대한 예는 전자레인지의 +30초 버튼이지만 사용자가 30초 대신 20초를 추가하기를 원하더라도!
, 전자레인지는 +30초 버튼만 제공합니다.
마음에 들지 않으면 전자레인지를 교체할 수밖에 없다.
방법 2를 예로 들면 사용자의 요구가 다양할 것으로 예상되는 경우 다음 인터페이스를 사용하십시오. 쿠팡의 그림은 이렇지만 저가, 고가, 저가격, 높은 별점 등 다양한 사용자의 니즈를 충족시키기 위해서는 방법 2와 마찬가지로 많은 책임감과 사용성이 뒷받침되어야 한다.
사용자에게 제공됩니다.
따라서 방법 2와 같이 API를 설계하는 것이 좋습니다.
그렇다면 인터페이스 계층과 로직 계층은 어떻게 연결해야 할까요? 첫째, 로직 레이어는 인터페이스 레이어의 변경에 영향을 받지 않아야 합니다.
따라서 minPrice를 min_price로 변경하면 어떻게 변경됩니까?FastAPI를 사용하여 쿼리를 발행하는 코드를 살펴보겠습니다.
# 인터페이스 레이어
from app.logic import ReadFruit
@app.get("/fruit/")
async def read_item(minPrice: int = 0):
data = ReadFruit().read(minPrice)
...
minPrice를 min_price로 변경하면 위의 코드는 아래 코드로 대체됩니다.
# 인터페이스 레이어
@app.get("/fruit/")
async def read_item(min_price: int = 0):
data = ReadFruit().read(min_price)
...
그러면 인터페이스 계층에서 로직 계층의 코드가 사용될 것이고, 로직 계층의 코드에서도 인터페이스에 해당하는 부분이 필요하다.
그렇다면 로직 레이어의 인터페이스는 어떻게 작성해야 할까요?
# 로직 레이어
class ReadFruit: # 과일 정보를 가져오는 로직의 인터페이스
def read(minPrice):
...
위와 같이 작성했다면 min_price를 minPrice에 전달하면 됩니다.
그러면 논리 계층의 코드를 수정할 필요가 없습니다.
그러나 인터페이스가 새 매개변수를 수신하면 어떻게 변경됩니까?
상황: max_price를 추가해야 합니다!
그렇다면 다음과 같이 수정해야 합니다.
# 인터페이스 레이어
@app.get("/fruit/")
async def read_item(min_price: int = 0, max_price: int = -1):
...
# 로직 레이어
class ReadFruit: # 추상화 되지 않은 로직의 인터페이스
def read(minPrice, maxPrice): # <--- maxPrice가 추가되는 수정이 발생!
!
...
즉, 인터페이스의 변경은 논리의 변경으로 이어집니다.
인터페이스 계층은 로직 계층에 단방향으로 의존하지만 인터페이스의 변화는 로직의 변화로 이어진다.
그렇게 되면 인터페이스의 자유로운 변경이 불가능해지고 서비스가 변경에 대응하기 어려워집니다.
로직 계층의 인터페이스를 추상화하여 이 문제를 어떻게 해결할 수 있습니까? 종속성을 분리하면 느슨한 결합이 가능합니다.
# 로직 레이어
from abc import ABCMeta, abstractmethod
class Reader(metaclass=ABCMeta):
@abstractmethod
def read():
pass
class MinMaxPriceReader(Reader):
def read(min_price, max_price):
pass
class MinPriceReader(Reader):
def read(min_price):
pass
class ReadFruit: # 추상화 된 인터페이스
def init(self, reader: Reader):
self.reader = reader
이와 같이 코드를 작성하면 기존 ReadFruit 클래스가 추상화가 됩니다.
즉, ReadFruit만 보면 어떻게 작동하는지 명확하지 않습니다.
다른 독자에 따라 작업이 다릅니다.
추상 인터페이스는 다음과 같이 사용됩니다.
# 인터페이스 레이어
from logic import ReadFruit, MinPriceReader
@app.get("/fruit/")
async def read_item(min_price: int = 0):
data = ReadFruit(reader=MinPriceReader).read(min_price)
...
이 구성에서 max_price 매개변수가 read_item에 추가되면 코드가 어떻게 변경됩니까? 논리적 수준에서 MinMaxPriceReader는 수정이 아닌 확장으로 추가되었습니다.
class MinMaxPriceReader(Reader):
def read(min_price, max_price):
pass
그리고 인터페이스 레이어를 다음과 같이 수정합니다.
from logic import ReadFruit, MinPriceReader, MinMaxPriceReader
@app.get("/fruit/")
async def read_item(min_price: int = 0, max_price: int = -1):
data = ReadFruit(reader=MinMaxPriceReader).read(min_price, max_price)
...
그러면 다음 변경이 발생하더라도 수정이 아닌 확장된 개념으로 변경을 수용할 수 있습니다.
또한 종속성 반전을 유지하고 인터페이스 분리 및 개방 폐쇄성을 유지합니다.