Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

레거시 시스템에 Django 들이밀기

638 views

Published on

크고 아름다운 Java 기반의 레거시 시스템. 하지만 매일 같이 반복되는 Java 코드를 찍어내기에 지쳤다면? 레거시 시스템에 Django를 들이밀어 한DB 두살림을 구축해보자. 아 그거 inspectdb 하나만 쓰면 되는 거 아닌가? 크고 작은 삽질들을 모아모아 공유합니다.

Published in: Software
  • Be the first to comment

레거시 시스템에 Django 들이밀기

  1. 1. 레거시 시스템에 Django 들이밀기 정지용
  2. 2. 발표자 소개 • P2P금융 렌딧에서 일합니다. • 새로운 언어와 프레임워크를 좋아합니다. • 읽기 편한 코드를 좋아합니다. • 가장 좋은 코드는 아예 만들 필요가 없는 코드 2
  3. 3. Django로 향하는 길을 함께 열어준 Sam.Jo에게 감사를 전합니다. 3
  4. 4. Java 세상 이야기 “우리 시스템에 OO한 기능을 추가하고 싶어요.” “일단 이.......만큼 코드를 쓰시고요.” MyExampleRepository.java MyExampleService.java MyExampleServiceImpl.java MyExampleController.java MyExampleAdminController.java MyExampleList.vue MyExampleDetail.vue 4
  5. 5. 자연스러운 수순 5
  6. 6. 이어질 만한 수순 "이 이벤트 배너 종료일자는 어떻게 고치나요?" "아.. 입력 폼 복붙하다가 필드를 하나 빼먹었네요." 6
  7. 7. 막장 수순 빨리 고쳐야하는데... 급하니까 일단 터미널을 열고... mysql에 접속해서... UPDATE event_banner SET ends_at = '2018-08-19 13:35:00'; 7
  8. 8. 계기 • 높은 생산성을 갖는 어드민을 새로 만들고 싶다... ... 8
  9. 9. 2017년 가을, 저희 개발팀의 상황 • 주요 개발 언어인 Java, Javascript가 코드 베이스의 대부분이고 Python을 일부분 사용 • 테이블 수는 200여개 • 기존 Java(Spring)으로 만든 거대한 어드민 운영중  두 개의 어드민을 계속 유지보수 할 수 있을까? 9
  10. 10. inspectdb • Integrating Django with a legacy database $ python manage.py inspectdb > models.py 10
  11. 11. inspectdb • DB 스키마를 읽어서 models.py를 생성해주는 기능 • legacy 시스템에 Django를 쉽게 도입할 수 있도록 도와줌 • 본격적으로 한 DB 두 살림을 운영해보자! 11
  12. 12. 목표 1. 최소한의 코딩으로 최대한의 효과(어드민 기능)를! 2. 최대한 기존 DB를 활용하지만, legacy 프로젝트에는 전혀 영향 을 주지 않도록 12
  13. 13. 발표 목차 • DB 연결 • 로그인 인증 • 모델 어드민 등록 및 커스터마이징 • inspectdb 확장하기 • BIT(1), BOOLEAN 문제 13
  14. 14. 주의: 전방에 코드가 있습니다. 14
  15. 15. DB 연결 • 두 개의 DB를 연결합니다. DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myproj_django', }, 'myproj': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myproj_development’, }, } DATABASE_ROUTERS = ['apps.router.DBRouter'] 15
  16. 16. inspectdb 업데이트 스크립트 • 모델을 자주 업데이트하니 아예 스크립트를 만듭시다. #!/bin/bash set -e TEMP_FILE=models.py.tmp python manage.py inspectdb --database myproj | tee ${TEMP_FILE} # 임시파일을 거쳐서 생성해야함. mv -f ${TEMP_FILE} apps/core/models.py 16
  17. 17. 로그인 인증 • django의 custom backend를 활용 • DB가 연동되어 있으니 legacy의 ID/PW 정보로 로그인 • 사용자, 권한 정보 등을 그대로 가져다 쓸 수 있음! # settings에서... AUTHENTICATION_BACKENDS = [ 'apps.core.backends.MyProjLoginBackend', ] 17
  18. 18. 로그인 인증 • 최초 로그인시 Django DB에도 staff user 생성 # backends.py class MyProjLoginBackend(ModelBackend): def authenticate(self, request=None, username=None, password=None): myproj_user = MyProjUser.objects.filter(username=username).first() # 암호 확인, 권한 확인 (생략) user = User.objects.filter(username=username).first() if not user: user = User.objects.create_user( username=myproj_user.username, email=lendit_user.email, is_staff=True, ) user.save() return user 18
  19. 19. 모델 어드민 등록 • 모델이 있으니, 모델 어드민만 등록하면 된대요! from django.contrib import admin from .models import * # Register your models here. admin.site.register(LoanContract) admin.site.register(User) admin.site.register(FeatureFlag) admin.site.register(AdminUser) admin.site.register(EventBanner) ... 19
  20. 20. 모델 어드민 등록 • models.py에 있는 것을 모두 등록할 거니까요. model_classes = [ x[1] for x in inspect.getmembers(sys.modules["apps.core.models"], inspect.isclass) if models.Model in x[1].__bases__ ] for model_class in model_classes: admin.site.register(model_class) 20
  21. 21. 완성! 참 쉽죠? 21
  22. 22. 짜잔!!!!!!!!!!!!!!!!!!!!!!!! 22
  23. 23. 짜잔!!!!!!!!!!!!!!!!!!!!!!!! 23
  24. 24. Django 어드민 커스터마이징 • Django의 손 쉬운 커스터마이징 • 하지만 200개 넘는 모델을 다 대응하려면... @admin.register(LoanContract) class LoanContractAdmin(admin.ModelAdmin): search_fields = ('=cust_nm',) list_display = ('id', 'cust_nm', 'status', 'created_at') list_filter = ('status',) form = LoanContractForm 24
  25. 25. Django 어드민 커스터마이징 • list_display 만이라도 전부 적용해봅시다. def generate_default_model_admin(model): return type(f'{model.__name__}Admin', (admin.ModelAdmin,), { 'list_display': [x.name for x in model._meta.get_fields()], }) # (생략......inspect로 model_class 불러오는 부분) for model_class in model_classes: if model_class not in admin.site._registry.keys(): admin.site.register(model_class, generate_default_model_admin(model_class)) 25
  26. 26. Django 어드민 커스터마이징 26
  27. 27. “필수 항목입니다.” • inspectdb는 문자열 필드를 만들 때, 모두 필수 필드라고 가정 • 모두 필수 아님 필드로 만들어봅시다. python manage.py inspectdb --database myproj > $TEMP_FILE sed " s/some_field = models.CharField(max_length=200)/some_field = models.CharField(max_length=200, blank=True)/; s/other_field = models.CharField(max_length=200)/other_field = models.CharField(max_length=200, blank=True)/; .... " $TEMP_FILE | tee $MODEL_FILE 27
  28. 28. inspectdb 확장하기 • sed 같은 외부 툴에 의존하지 않는 방법은 없을까? • Github을 뒤적이던 도중.... 🤔 흥미로운 파일명이군요. 28
  29. 29. inspectdb 확장하기 • Django문서 중 custom management commands # apps/core/management/commands/inspectdb.py from django.core.management.commands.inspectdb import ( Command as InspectDBCommand, ) class Command(InspectDBCommand): def get_field_type(self, connection, table_name, row): field_type, field_params, field_notes = super().get_field_type(connection, table_name, row) if field_type == 'CharField': field_params['blank'] = True return field_type, field_params, field_notes 29
  30. 30. inspectdb 확장하기 • auto_now를 써서 생성, 수정 일자도 자동으로 넣어봅시다. def get_field_type(self, connection, table_name, row): field_type, field_params, field_notes = super().get_field_type(connection, table_name, row) if row.name == 'created_at': field_params['auto_now_add'] = True elif row.name == 'updated_at': field_params['auto_now'] = True if field_type == 'CharField': field_params['blank'] = True return field_type, field_params, field_notes 30
  31. 31. mysql, 그리고 BIT(1) • 기존 시스템은 Mysql을 사용 • Boolean 값을 BIT(1)으로 표시 • inspectdb는 BIT(1)을 어떻게 생각할까? old_bit_field = models.TextField() # This field type is a guess. 31
  32. 32. 커스텀 Boolean Field • 사용자 필드 생성 매뉴얼을 정독하고, 만들어봅시다. class LBooleanField(BooleanField): def from_db_value(self, value, expression, connection, context): if value is None: return False return self.to_python(value) def to_python(self, value): # BIT(1)은 b'x00' b'x01'로 떨어짐. 변환필요. if isinstance(value, bytes): return bool(value[0]) return super(BooleanField, self).to_python(value) 32
  33. 33. 커스텀 Boolean Field • inspectdb에서 불러다 씁시다. • row.null_ok 를 사용하여 NullBooleanField도 확장하면 됩니다. def get_field_type(self, connection, table_name, row): field_type, field_params, field_notes = super().get_field_type(connection, table_name, row) if (row.type_code == FIELD_TYPE.TINY or row.type_code == FIELD_TYPE.BIT) and row.internal_size == 1: field_type = 'LBooleanField' field_notes = [] if row.name == 'created_at': field_params['auto_now_add'] = True ...(생략) 33
  34. 34. 여기까지 쓴 Python 코드 • 로그인 처리: 10여줄 • 모델 별 admin 등록: 10여줄 • inspectdb 확장: 30여줄 • DB 라우터: 20여줄 34
  35. 35. 돌아보면... • Legacy와의 공존은 성공 • Django기반 시스템을 발전시킬 수 있는 기반을 마련함 • 가장 힘들었던 부분: 배포 환경 설정 35
  36. 36. 돌아보면... • 날로 먹으면 기분이 좋다. • 거의 모든 부분이 확장 가능한 Django. • 지금 복사 붙여넣기를 하고 있다면, 분명 더 나은 방법이 있다. 36

×