version: '3'
services:
  rabbitmq:
    image: rabbitmq:3-management-alpine
    container_name: rabbitmq-stream
    volumes:
      - ./.docker/rabbitmq/etc/:/etc/rabbitmq/
      - ./.docker/rabbitmq/data/:/var/lib/rabbitmq/
      - ./.docker/rabbitmq/logs/:/var/log/rabbitmq/
    ports:
      - "5672:5672"
      - "15672:15672"
    environment:
      RABBITMQ_ERLANG_COOKIE: "RabbitMQ-My-Cookies"
      RABBITMQ_DEFAULT_USER: "admin"
      RABBITMQ_DEFAULT_PASS: "rabbitpassword"

docker-compose.yml 파일은 위와 같다. 옵션별로 무엇을 의미하는지 알아보자


image: rabbitmq:3-management-alpine

rabbitmq 이미지중에 3-management-alpine 버전을 선택하겠다는 의미이다. 3-management-alpine 버전은 두가지 특징이 있다

  • management : 관리자 UI를 사용할 수 있게 해주는 management plugin이 설치되어 있는 이미지이다. 기본 관리자 username/passwdguest/guest 이다
  • alpine : 경량 리눅스 배포판 이미지, 사용하는 이유는 링크에 자세히 설명되어 있다.

volumes:
  - ./.docker/rabbitmq/etc/:/etc/rabbitmq/
  - ./.docker/rabbitmq/data/:/var/lib/rabbitmq/
  - ./.docker/rabbitmq/logs/:/var/log/rabbitmq/

volumes 는 도커 컨테이너는 실행 후 컨테이너를 삭제하면 존재하던 데이터가 모두 사라지게 되기 때문에 마운트를 해주는데, 이에 필요한 옵션이다.

만약 ./.docker/rabbitmq/etc/:/etc/rabbitmq/ 이라면 로컬의 ./.docker/rabbitmq/etc/디렉토리를 컨테이너의 /etc/rabbitmq/ 디렉토리와 마운트 하겠다는 의미이다.

한줄 한줄 마운트한 이유를 설명하자면

  • ./.docker/rabbitmq/etc/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf : RabbitMQ configuration 파일을 설정하기 위함
  • ./.docker/rabbitmq/data/:/var/lib/rabbitmq/ : RabbitMQ 데이터에 접근하기 위함
  • ./.docker/rabbitmq/logs/:/var/log/rabbitmq/ : RabbitMQ 로그에 접근하기 위함

environment:
  RABBITMQ_ERLANG_COOKIE: "RabbitMQ-My-Cookies"
  RABBITMQ_DEFAULT_USER: "admin"
  RABBITMQ_DEFAULT_PASS: "rabbitpassword"

컨테이너 내부의 환경 변수를 설정하는 옵션이다.

  • RABBITMQ_ERLANG_COOKIE : RabbitMQ 클러스터를 구성할 때 노드끼리 동일하게 맞춰줘야 하는 값
  • RABBITMQ_DEFAULT_USER : management UI 에서 로그인 username
  • RABBITMQ_DEFAULT_PASS : management UI 에서 로그인 password

ref.

https://zgadzaj.com/development/docker/docker-compose/containers/rabbitmq

6월 2일에 날라온걸 지금 쓰고있다...

친구가 지원하길래 재밌어 보여서 지원했던 우아한 테크캠프의 2차 코딩테스트 합격 이메일이 날라왔다.

 

문제는 API 명세서를 보고 간단한 로그인/로그아웃 구현하고 테이블을 만드는 예제였다.

우선 문제를 받자마자 언제나 그랬듯이 아키텍처에 대한 고민을 시작했다.

 

난생 처음 접해보는 프론트 코드를 짜야 해서 막연한 부분이 있긴 했지만 확장성DRY 두 가지만 생각하며 구조를 작성하고자 했다.

지금 이 문제를 받는다면 엔티티부터 정의하고 시작할 거 같다

 

두 가지중 확장성에 가장 큰 비중을 두고 구조를 작성했다.

 

users, posts, articles 와 같이 기능으로 폴더를 분리하기 보다 controller, models, views 처럼 역할로 폴더를 분리하는 것을 선호하는 스타일 (인터페이스를 만들어 다형성을 적용하기 편함) 이기에 역할로 폴더를 분리했다.

 

프로젝트 구조 고민에만 1시간은 잡아먹은듯 하다.

 

다음으로 과제에서 제공되는 API 명세서를 보고 fetch 혹은 axios로 요청해 검색 기능(간단한 Like 서치)이 있는 테이블을 구현해야 했는데, 익숙하지 않은 언어에 DRY를 지키려 노력하며 코딩하다 보니까 개발 속도가 나오지 않았다. 

 

프론트엔드 입장에서 개발하면서 느낌점이 몇가지 있는데, 아래와 같다.

  1. Pagination은 서버에서 무조건 제공해 줘야 하는구나 (너무 많은 정보를 bulk 조회 해버리면 로직 속도가 매우 느려질 것 같다.)
  2. 알고리즘을 의식의 흐름대로 작성할 수 있는 콜백함수가 마음에 들었다.
  3. 자바스크립트의 세미콜론은 왜 있는걸까
  4. 파이썬 하고싶다

 

내년에도 문제가 같다면 jQuery, fetch, JS의 각종 내장 함수 등은 숙지하고 테스트를 보는 편이 좋을 거 같다.


4시간동안 집중하먼서 풀었지만, 결국 필수 조건만 만족시키고 부가 조건은 만족시키지 못했다.

막상 끝나니 중간에 잠깐 쉬었던 시간이 아깝게 느껴졌다.

 

어찌보면 새로운 도전이었는데, 크게 느낀 것이 안 될거 같아도 일단 고민이라도 해보자는 것이다.

 

그래서 결국 2차 과제를 합격했는데 결과물은 마음에 들지 않았다.


최근에 클린 아키텍처에 관한 책을 읽으며 들은 생각인데, 내가 알고있던 아키텍처가 각 레이어 간의 종속성을 많이 위배한다는 사실을 알게 되었다. ex) 인터페이스 어댑터 레이어에서 로그인 로직을 처리함

또한 여러 에지케이스를 처리하기 위해 프로젝트 구조가 예기치 못한 많은 변경을 가졌는데 이에 대처하는 방법을 알고 싶어졌다.

import peewee

class MySQLModel(peewee.Model):
    @property
    @classmethod
    def unique_fields(cls) -> list:
        for field_name in cls._meta.fields.keys():
            field = getattr(cls, field_name)
            if field.unique == True or field.primary_key == True:
                yield field

peewee 모델의 unique한 field의 이름을 iterable한 객체로 반환하는 property 함수를 만들었다.

key in [field.name for field in MySQLModel.unique_fields]

그런데 위와 같이 사용 할 때 TypeError: 'property' object is not iterable 에러가 발생했다.

해결방법을 찾아보니 metaclass에서 property 함수를 만드는 방법, class decorator를 직접 만들어서 처리하는 방법 등이 있었다.

class decorator를 직접 만들어서 처리하는 방법은 아래와 같은데, 이게 마음에 들었다.


class classproperty(object):
    def __init__(self, function):
        self.function = function

    def __get__(self, owner_self, owner_cls):  # classproperty 객체에 접근할 때 inner_func 결과값을 반환하도록
        return self.function(owner_cls)


class MySQLModel(peewee.Model):
    @classproperty
    def unique_fields(cls) -> list:
        for field_name in cls._meta.fields.keys():
            field = getattr(cls, field_name)
            if field.unique == True or field.primary_key == True:
                yield field

docker-compose로 개발환경을 구축하기 위해 아래와 같이 docker-compose.dev.yml 파일을 작성했었다.

version: "2"

services:
  db:
    image: mysql:5.7.34
    container_name: dorandoran_db

    ports:
      - "7001:3306"
    environment:
      - MYSQL_DATABASE=dorandoran_dev_db
      - MYSQL_USER=devuser
      - MYSQL_PASSWORD=password
      - MYSQL_ROOT_PASSWORD=password
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

  web:
    build:
      context: .
      dockerfile: ./compose/develop/Dockerfile-dev
    volumes:
      - ./:/app/
    command:
      - bash
      - -c
      - |
        python dorandoran/manage.py migrate
        python dorandoran/manage.py runserver 0.0.0.0:8000
    container_name: dorandoran_web
    env_file:
      - dev.env
    depends_on:
      db:
        condition: service_healthy
    restart: always
    ports:
      - "8000:8000"

분명 webdb에 의존하고 있음을 명시하고 healthcheck까지 붙여줬는데 왜 안되는지 이해가 안됐다.

문제는 dev.env 파일에 있었다.

DJANGO_DB_HOST=db
DJANGO_DB_PORT=7001  # 이곳
DJANGO_DB_NAME=dorandoran_dev_db
DJANGO_DB_USERNAME=root
DJANGO_DB_PASSWORD=password

web 컨테이너가 db 컨테이너에 접근 할 때의 포트를 외부 포트(7001)로 지정해 줬던 것이다.
내부 포트인 3306으로 바꾸니 제대로 동작하였다.

DJANGO_DB_HOST=db
DJANGO_DB_PORT=3306  # 잘 된다!
DJANGO_DB_NAME=dorandoran_dev_db
DJANGO_DB_USERNAME=root
DJANGO_DB_PASSWORD=password

depends_on

Express dependency between services. Service dependencies cause the following behaviors:

  • docker-compose upstarts services in dependency order. In the following example,dbandredisare started beforeweb.
  • docker-compose up SERVICEautomatically includesSERVICE’s dependencies. In the example below,docker-compose up webalso creates and startsdbandredis.
  • docker-compose stopstops services in dependency order. In the following example,webis stopped beforedbandredis.

docker-compose up 은 순서대로 서비스를 실행하는데, depends_on이 여기에 영향을 미친다.

version: "3.7"

services:
  web:
    build: .
    depends_on:
      - db

  db:
    image: mysql:8.0.22
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci

예를 들어 위와 같은 docker-compose 파일이 있다면, db 서비스가 먼저 실행되고, web 서비스가 그 다음으로 실행된다.

출처

Docker doDocker docscs

하위 디렉토리의 모든 테스트를 실행하고 싶을 때가 있다.

 

그럴때는

$ python -m unittest discover

를 입력하면 하위 디렉토리모든 테스트를 실행한다. (기본적으로 "test*.py" 포맷의 파일을 찾아 실행한다.)

 

$ python -m unittest discover 
F.
======================================================================
FAIL: test_failure (test2.HelloTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\workspace\study\TIL\test2.py", line 9, in test_failure
    self.assertEqual(0,1)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
recursion = int(input())
print("어느 한 컴퓨터공학과 학생이 유명한 교수님을 찾아가 물었다.")

def q(recursion,cnt):
    print(cnt * "____" + "\"재귀함수가 뭔가요?\"")
    if recursion <= 0:
        print(cnt * "____" + "\"재귀함수는 자기 자신을 호출하는 함수라네\"")
    else:
        print(cnt * "____" + "\"잘 들어보게. 옛날옛날 한 산 꼭대기에 이세상 모든 지식을 통달한 선인이 있었어.")
        print(cnt * "____" + "마을 사람들은 모두 그 선인에게 수많은 질문을 했고, 모두 지혜롭게 대답해 주었지.")
        print(cnt * "____" +"그의 답은 대부분 옳았다고 하네. 그런데 어느 날, 그 선인에게 한 선비가 찾아와서 물었어.\"")
    
        q(recursion - 1, cnt + 1)
    print(cnt * "____" + "라고 답변하였지.")

q(recursion, 0)

 

Django 로 개발을 하던 중 auth 라는 이름을 가진 Custom app을 INSTALLED_APP에 추가하니 오류가 발생했다.

기존의 "django.contrib.auth" 앱과 중복되었기 때문이다.

 

 

https://code.djangoproject.com/ticket/21562

 

#21562 (Bad things happen if you name your custom user app "auth") – Django

Bad things happen if you name your custom user app "auth" Reported by: Charlie DeTar Owned by: nobody Component: Documentation Version: dev Severity: Normal Keywords: Cc: Triage Stage: Accepted Has patch: yes Needs documentation: no Needs tests: no Patch n

code.djangoproject.com

새로운 이름으로는 accounts가 적절 할 것 같다.

이름 변경 후에 users 앱과 통합시켜야 겠다.

 

깃허브 링크

https://github.com/Doran-Doran-development/DoranDoran-Server-2

 

Doran-Doran-development/DoranDoran-Server-2

🧮 교내 실습실 예약 및 관리 웹 서비스 '도란도란'의 서버를 개발한 레포지토리 입니다. Contribute to Doran-Doran-development/DoranDoran-Server-2 development by creating an account on GitHub.

github.com

 

+ Recent posts