URI(Uniform Resource Identifier) : 네트워크 상의 리소스 식별자
URL(Uniform Resource Locator) : 네트워크 상의 리소스의 주소(위치)
URI는 URL을 포함하는 상위 개념이다.
그림 출처 : URI랑 URL 차이점이 뭔데? <찰스의 안드로이드>
위 그림에서 두 방식이 결국 index.html을 가리키는 것은 동일하지만, URL이 index.html의 위치를 직접적으로 명시하는 것과 달리, URI는 서버에서 'index.html로 접근하라'는 요청을 처리(식별)할 수 있도록 하는 형태이다.
따라서 Django url conf는 특정 'URI'를 받아 정해진 API를 호출하는 방법을 설정하는 셈이다.
Url Dispatcher - Djangoproject 공식 문서
path parameter 설정과 urls.py 내부 활용 방법을 잘 정리한 공식 문서가 있다.
예를 들어 상품 페이지로부터 3번 상품의 정보를 조회하는 요청(URI는 '도메인/products/3' 이라는 형식으로 미리 약속했다)이 들어왔다고 치자.
이때, 상품 정보 조회 API는 products 앱 내에 있다.
# 프로젝트 최상단 urls.py
urlpatterns = [
path('products', include('products.urls')),
]
최상단 urls의 path 내의 include 함수는 URI에서 지정된 문자열을 읽어내고, 그 이후의 문자열만을 잘라내어 지정된 url conf로 보낸다.
즉, '/3' 이라는 문자열만이 pruducts 앱의 urls.py로 보내진다는 뜻이다.
# products 앱 내 urls.py
urlpatterns = [
path('/<int:product_id>', ProductView.as_view()),
]
이렇게 받은 '/3'은 products 앱 내 urls에 설정된 path에게 인식되고, '3' 부분은 URL dispatcher에 의해 변수명이 product_id인 int형 자료로 선언되어 ProductView 클래스내 함수의 변수로 입력된다.
이런 과정이 필요한 이유는 비슷한 형태의 요청이 반복되기 쉽기 때문이다. 상품 정보 조회 요청은 7번 상품으로도 요청될 수 있고, 1500번 상품으로도 요청이 올 수 있다.
# views.py
class ProductView(View):
def get(self, request, product_id):
if not Product.objects.filter(id=product_id).exists():
return JsonResponse({"message":"PRODUCT_DOES_NOT_EXIST"}, status=400)
product = Product.objects.get(id=product_id)
return JsonResponse({"name": product.name}, status=200)
위와 같이 GET method로 '도메인/products/<product_id>' URI로 요청을 보내면, 해당 id를 가진 상품의 이름을 DB로부터 조회하여 응답한다..
path parameter를 통해 개별 상품의 정보를 조회하는 것은 좋다. 하지만 빨강색 상품을 전부 보고 싶다거나(필터링), 낮은 가격순으로 상품들은 보고 싶을 때(정렬)는 어떻게 해야할까?
GET method를 통해 요청하는 것이므로 request body에 그런 정보들을 담을 수는 없고, path parameter를 설정한다고 해도, 근본적으로 같은 기능을 하는 API를 일일이 만들어서 각각의 경우에 대해 url conf로 길을 돌려주는 것은 비효율적이다. ...그리고 필터가 여러 개면 어떻게 길을 만들어야하지?
이때 활용하는 것이 URI 뒷 부분 '?'로 시작하는 Query parameter이다.
# products 앱 내 urls.py
urlpatterns = [
path('', ProductListView.as_view()),
path('/<int:product_id>', ProductView.as_view()),
]
'도메인/products'로 GET method 요청을 보내면 모든 상품의 리스트를 응답하는 API를 추가했다.
여기에 query parameter로 빨강색(color id = 1)인 상품만을 필터하여 응답하는 방식을 추가할 수 있다.
# views.py
class ProductListView(View):
def get(self, request):
color = request.GET.getlist('color', None)
products = Product.objects.all()
if color:
color_filter = Color.objects.filter(id__in=color)
products = products.filter(color__in=color_filter)
result = [{
"id" : product.id,
"image_url" : product.image_url,
"name" : product.name,
"price" : int(product.price),
"manufacture_date" : product.manufacture_date,
"stock" : sum([option.stock for option in product.productoption_set.all()])
} for product in products]
return JsonResponse({"response":result}, status=200)
'도메인/products?color=1' 로 GET method로 요청을 하면, 요청에 Key = 'color' : Value = '1' 이라는 query parameter가 추가되어 전송된다.
ProductListView 클래스 내 get 함수에서는 이를 GET.get(Key의 Value가 하나) 혹은 GET.getlist(같은 키의 Value가 복수, 리스트형) 을 통해 쿼리 변수를 잡아내어 color라는 변수명으로 선언한다.
이렇게 잡아낸 쿼리 변수를 가지고 원하는 방식의 로직으로 다룰 수 있다.
Query parameter의 장점은 같은 기능(API)의 여러 종류의 경우의 수를 병렬적으로 다룰 수 있다는 데에 있다.
색깔뿐만이 아니라 사이즈, 가격범위같은 필터링까지 추가하고 싶다면, 같은 형태의 필터 로직을 추가하고, '도메인/products?color=1&size=3&price=5' 같은 방식으로 쿼리 변수를 추가로 붙여 요청을 전송하면 된다.
쿼리 변수를 활용한다면 필터뿐만 아니라 정렬(sort='기준' or '-기준'), 나열 개수(offset=m&limit=n) 같은 기능도 활용할 수 있다.
Path parameter는 개별 정보를 다룰 때, Query parameter는 filtering, sorting, pagination 등 한번에 여러 개의 정보를 다룰 때 유용하다.
이해가 쏙쏙 잘 되게 설명을 잘 적어주셨네요! 감사합니다~ 😊