기록용이며, 개인적으로 찾아보고 연구한 결과다. 아마 더 좋은 Approach가 많이 있을 것이라 생각한다.
게시판이 있는 사이트에서 Chat 관련 앱을 만들고 있을때, Topic으로써 텍스트, 유저, 또는 게시글을 참조할 수 있도록 만들고 싶은 경우다.
GenericForeginKey
처음으로 했던 방식은
GenericForeignKey
였다. Django 문서를 뒤져가면서 발견한 것으로, 여러 테이블을 조합해 Django가 알아서 해주는 방식이였다.from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType class ChatRoom(models.Model): topic_type = models.ForeignKey(ContentType, on_delete=models.SET_NULL, null=True, blank=True) topic_id = models.PositiveIntegerField(null=True, blank=True) topic_text = models.TextField(blank=True)
이 방식에는 문제가 있었는데,
ContentType
은 결국 어떤 Model을 직접적으로 가르키는지 정의가 되있지 않았다.이후 추가적으로 조사결과, Avoid Django's GenericForeignKey 라는 글을 찾을 수 있었다. 요약을 하자면 암묵적인 방식으로써의 접근이기 때문에 아래의 문제가 있다고 한다.
- Database Design의 문제
- 참조 무결성
- 성능 이슈
결국 해당글을 자체적으로 구현해보도록 했다.
@property
와 IntegerChoices
사실상 결국
GenericForeignKey
의 자체 구현버전이 되어버렸다. 아직도 연구중인 상태다.class Topic(models.IntegerChoices): TEXT = 1 USER = 2 POST = 3 class ChatRoom(models.Model): topic_type = models.IntegerField(choices=Topic.choices) topic_text = models.TextField(blank=True) topic_id = models.PositiveIntegerField(null=True, blank=True) @cached_property def topic(self): match self.topic_type: case Topic.TEXT: return self.topic_text case Topic.USER: return User.objects.get(pk=self.topic_id) case Topic.POST: return Post.objects.get(pk=self.topic_id) case _: raise AssertionError("Unknown Topic Type") @classmethod def set_topic(self, instance): def cleanup(): # topic이 한번도 불러와지지 않은 경우 AttributeError try: del self.topic except AttributeError: pass self.topic_id = None self.topic_text = "" match instance: case str(): cleanup() self.topic_type = Topic.TEXT self.topic_text = instance case User(): cleanup() self.topic_type = Topic.USER self.topic_id = instance.id case Post(): cleanup() self.topic_type = Topic.POST self.topic_id = instance.id case _: return False return True
@property
를 사용하여 자연스러운 topic 접근을 유도했다. 다만 SQL 쿼리를 날리는 등의 Heavy한 행동이 있기 때문에 @cached_property
및 set_topic
시 해당 cache를 삭제하는 식으로 진행한다.그나마 다행인점은, 이쪽이 좀 더 자체적으로 데이터 베이스를 컨트롤 하기는 쉽다는거다.
그리고 참조하는 데이터가 삭제되더라도 채팅방이 사라지면 안되기 때문에, 관계성과
on_delete
는 신경쓰지 않았다.아직 연구중이기 때문에 다음과 같은 문제점이 남아있다.
- 해당 Topic이 가르키는 데이터 삭제시 핸들링.
- 데이터 추가시 매번 Choices가 늘어나는 문제.
더 연구하여 언젠가 블로그에도 업데이트 예정.