مدیریت تنظیمات در جنگو

امیرحسین بیگدلو 12 ماه قبل

این مقاله برای مهندسانی در نظر گرفته شده است که از چارچوب جنگو استفاده می کنند. این مقاله بینش عمیقی در مورد پیکربندی تنظیمات پروژه جنگو و جوانب مثبت و منفی رویکردهای مختلف به شما می دهد. در این مقاله، توصیه هایی در مورد ابزارها، بهترین روش ها و راه حل های معماری خواهید یافت که تماماً توسط پروژه های موفق آزمایش شده و اثبات شده اند.

 

مطالب این صفحه:

مدیریت تنظیمات جنگو: مشکلات

مدیریت تنظیمات جنگو: رویکردهای مختلف

راه حل 12Factors

پکیج djnago-environ

ساختار فایل های settings

نام گذاری اطلاعات

جمع بندی

 

# مدیریت تنظیمات جنگو: مشکلات

 

1 محیط های متفاوت

معمولاً، شما چندین محیط مختلف برای اجرای برنامه دارید: local، dev، ci، qa، staging، production، و غیره. هر محیط می‌تواند تنظیمات خاص خود را داشته باشد (به عنوان مثال: DEBUG = True، گزارش‌گیری مفصل‌تر، برنامه‌های اضافی، برخی داده‌های اضافی و غیره) . شما به رویکردی نیاز دارید که به شما امکان می دهد تمام این تنظیمات جنگو را حفظ کنید.

 

2 اطلاعات حساس

شما در هر پروژه جنگو SECRET_KEY دارید. علاوه بر این، رمزهای عبور و توکن های DB برای API های شخص ثالث مانند آمازون یا توییتر وجود دارد. این داده ها را نمی توان در VCS ذخیره کرد.

 

3 اشتراک گذاری تنظیمات بین اعضای تیم

برای از بین بردن خطای انسانی هنگام کار با تنظیمات، به یک رویکرد کلی نیاز دارید. به عنوان مثال، یک توسعه‌دهنده ممکن است یک برنامه شخص ثالث یا یکپارچه‌سازی API اضافه کند و تنظیمات خاصی را اضافه نکند. در پروژه های بزرگ (یا حتی متوسط)، این می تواند مشکلات واقعی ایجاد کند.

 

4 تنظیمات جنگو کدهای پایتون هستند

این یک نفرین و در عین حال یک نعمت است. این به شما انعطاف‌پذیری زیادی می‌دهد، اما همچنین می‌تواند مشکل‌ساز باشد – به جای جفت‌های کلید-مقدار، settings.py می‌تواند منطق بسیار دشواری داشته باشد.

 

دوره پیشنهادی: دوره آموزش رایگان جنگو

 

# مدیریت تنظیمات جنگو: رویکردهای مختلف

هیچ روش مشخصی برای پیکربندی تنظیمات جنگو وجود ندارد. اما کتاب‌ها، پروژه‌های متن‌باز و کاری، توصیه‌ها و رویکردهای زیادی را در مورد بهترین انجام آن ارائه می‌دهند. بیایید نگاهی کوتاه به محبوب ترین ها بیندازیم تا نقاط ضعف و قوت آنها را بررسی کنیم.

 

+ فایل settings_local.py

این قدیمی ترین روش است. من از آن زمانی استفاده کردم که برای اولین بار یک پروژه جنگو را روی سرور پیکربندی می کردم. من دیدم که بسیاری از مردم در گذشته از آن استفاده می کنند، و هنوز هم آن را می بینم.

 

ایده اصلی این روش، گسترش تمام تنظیمات محیطی خاص در فایل settings_local.py است که توسط VCS نادیده گرفته می شود. در اینجا یک مثال است:

## -- settings.py

ALLOWED_HOSTS = ['example.com']
DEBUG = False
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'production_db',
        'USER': 'user',
        'PASSWORD': 'password',
        'HOST': 'db.example.com',
        'PORT': '5432',
        'OPTIONS': {
            'sslmode': 'require'
        }
    }
}

...

from .settings_local import *

 

فایل settings_local.py

## -- settings_local.py

ALLOWED_HOSTS = ['localhost']
DEBUG = True
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'local_db',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

 

مزیتی که این روش دارد اینست که دیگر اطلاعات حساس در vcs قرار نمیگیرد. اما معایب این روش، از دست دادن اطلاعاتی است که در فایل settings_local.py قرار داده‌اید و همچنین نیاز است فایل مانند settings_local.example داشته باشید که بقیه برنامه نویسان به مقادیر پیشفرض دسترسی داشته باشند.

 

 

+ جداکردن فایل settings مربوط به هر محیط

این روش تکمیل شده رویکرد قبلی است. این روش به شما امکان می دهد تمام تنظیمات را در VCS نگه دارید و تنظیمات پیش فرض را بین توسعه دهندگان به اشتراک بگذارید. در این مورد، چندین فایل وجود دارد که پروژه های جنگو از آنها تنظیمات را دریافت می کنند و شما یک بسته تنظیمات با ساختار فایل زیر ایجاد می کنید:

settings/
   ├── __init__.py
   ├── base.py
   ├── ci.py
   ├── local.py
   ├── staging.py
   ├── production.py
   └── qa.py

 

مثلا فایل settings/local.py به شکل زیر خواهد بود:

## -- settings/local.py

from .base import *


ALLOWED_HOSTS = ['localhost']
DEBUG = True
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'local_db',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

 

برای تعیین اینکه برای پروژه ای که اجرا می کنید از کدام پیکربندی جنگو استفاده کند، باید یک پارامتر اضافی تنظیم کنید:

python manage.py runserver --settings=settings.local

 

از مزایای این روش اینست که تمام فایل ها در vcs قرار دارند و به آسانی میتوانید تنظیمات را بین برنامه نویسان مختلف به اشتراک بگذارید. اما مشکلی که این روش دارد اینست که باید یک روش برای مدیریت اطلاعات حساس و توکن ها پیدا کنید و همچنین استفاده از وراثت در تنظیمات نگهداری کد را سخت میکند.

 

 

+ استفاده از متغیرهای محیطی

برای حل مشکل داده های حساس، می توانید از متغیرهای محیطی استفاده کنید.

import os


SECRET_KEY = os.environ['SECRET_KEY']
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['DATABASE_NAME'],
        'HOST': os.environ['DATABASE_HOST'],
        'PORT': int(os.environ['DATABASE_PORT']),
    }
}

 

این ساده ترین مثال با استفاده از os.environ پایتون است و چندین مشکل دارد:

1. مجبورید خطای KeyError را هندل کنید.

2. مجبورید به شکل دستی داده ها را تغییر دهید.(به DATABASE_PORT نگاه کنید)

 

برای حل مشکل KeyError میتوانید از فانکشن زیر استفاده کنید:

import os

from django.core.exceptions import ImproperlyConfigured


def get_env_value(env_variable):
    try:
      	return os.environ[env_variable]
    except KeyError:
        error_msg = 'Set the {} environment variable'.format(var_name)
        raise ImproperlyConfigured(error_msg)


SECRET_KEY = get_env_value('SECRET_KEY')
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': get_env_value('DATABASE_NAME'),
        'HOST': get_env_value('DATABASE_HOST'),
        'PORT': int(get_env_value('DATABASE_PORT')),
    }
}

 

همچنین، می‌توانید مقادیر پیش‌فرض را برای فانکشن get_env_value تنظیم کنید و تبدیل نوع را اضافه کنید. اما در واقع نیازی به نوشتن این فانکشن نیست، زیرا می توانید از یک کتابخانه شخص ثالث استفاده کنید (در ادامه در مورد این موضوع صحبت خواهیم کرد).

 

مزایای این روش اینست که تنظیمات از کدها جدا شده‌اند و از یک کد برای متغیرهای محیطی استفاده میکنید و همچنین از وراثت هم در تنظیمات استفاده نمیکنید. اما مشکلی که وجود دارد اینست که هنوز هم باید اطلاعات را بین برنامه نویس های دیگر به اشتراک بگذارید.

 

دوره پیشنهادی: دوره آموزش رایگان پایتون

 

# راه حل 12Factors

12Factors مجموعه‌ای از توصیه‌ها در مورد نحوه ساخت برنامه‌های وب توزیع‌شده است که به آسانی قابل استقرار و مقیاس‌بندی در Cloud هستند. این روش توسط Heroku، یک ارائه دهنده معروف میزبانی ابری ایجاد شده است. همانطور که از نام آن پیداست، این مجموعه شامل دوازده قسمت است:

  1. Codebase
  2. Dependencies
  3. Config
  4. Backing services
  5. Build, release, run
  6. Processes
  7. Port binding
  8. Concurrency
  9. Disposability
  10. Dev/prod parity
  11. Logs
  12. Admin processes

 

هر بخش یک روش توصیه شده برای اجرای یک جنبه خاص از پروژه را توصیف می کند. برخی از این نقاط توسط سازهایی مانند جنگو، پایتون، pip پوشش داده می شوند. برخی از آنها توسط الگوهای طراحی یا راه اندازی زیرساخت پوشانده شده اند. در زمینه این مقاله، ما به یک بخش علاقه داریم: configuration.

 

قانون اصلی configuration ذخیره تنظیمات در محیط است. پیروی از این توصیه به ما امکان جداسازی دقیق پیکربندی از کد را می دهد. برای اطلاعات بیشتر میتوانید به 12factor.net مراجعه کنید.

 

ویدیو پیشنهادی: کار با ChoiceField در جنگو

 

# پکیج django-environ

بر اساس موارد فوق، می بینیم که متغیرهای محیطی مکان مناسبی برای ذخیره تنظیمات هستند. اکنون وقت آن است که در مورد جعبه ابزار صحبت کنیم. نوشتن کد با استفاده از os.environ گاهی اوقات دشوار است و برای رسیدگی به خطاها به تلاش بیشتری نیاز دارد. بهتر است به جای آن از django-environ استفاده کنید.

 

این برنامه یک API با عملکرد خوب برای خواندن مقادیر از متغیرهای محیطی یا فایل‌های متنی، تبدیل نوع دسته‌ای و غیره ارائه می‌دهد. بیایید به چند نمونه نگاه کنیم.

 

فایل settings.py جنگو قبل از django-environ:

import os


SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

DEBUG = True
TEMPLATE_DEBUG = DEBUG

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'production_db',
        'USER': 'user',
        'PASSWORD': 'password',
        'HOST': 'db.example.com',
        'PORT': '5432',
        'OPTIONS': {
            'sslmode': 'require'
        }
    }
}

MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets')
MEDIA_URL = 'media/'
STATIC_ROOT = os.path.join(SITE_ROOT, 'static')
STATIC_URL = 'static/'

SECRET_KEY = 'Some-Autogenerated-Secret-Key'

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': '127.0.0.1:6379/1',
    }
}

 

فایل settings.py جنگو بعد از django-environ:

import environ


root = environ.Path(__file__) - 3  # get root of the project
env = environ.Env()
environ.Env.read_env()  # reading .env file

SITE_ROOT = root()

DEBUG = env.bool('DEBUG', default=False)
TEMPLATE_DEBUG = DEBUG

DATABASES = {'default': env.db('DATABASE_URL')}

public_root = root.path('public/')
MEDIA_ROOT = public_root('media')
MEDIA_URL = env.str('MEDIA_URL', default='media/')
STATIC_ROOT = public_root('static')
STATIC_URL = env.str('STATIC_URL', default='static/')

SECRET_KEY = env.str('SECRET_KEY')

CACHES = {'default': env.cache('REDIS_CACHE_URL')}

 

فایل env. به شکل زیر خواهد بود:

DEBUG=True
DATABASE_URL=postgres://user:password@db.example.com:5432/production_db?sslmode=require
REDIS_CACHE_URL=redis://user:password@cache.example.com:6379/1
SECRET_KEY=Some-Autogenerated-Secret-Key

 

 

# ساختار فایل های settings

به جای تقسیم تنظیمات بر اساس محیط، می توانید آنها را بر اساس منبع تقسیم کنید: جنگو، برنامه های شخص ثالث (Celery، DRF، و غیره) و تنظیمات سفارشی خود.

project/
├── apps/
├── settings/
│   ├── __init__.py
│   ├── djano.py
│   ├── project.py
│   └── third_party.py
└── manage.py

 

در فایل init.py باید به شکل زیر باشد:

from .django import *       # All Django related settings
from .third_party import *  # Celery, Django REST Framework & other 3rd parties
from .project import *      # You custom settings

 

همچنین هر ماژول می تواند به عنوان یک پکیج ایجاد شود، و شما می توانید آن را به صورت دانه بندی بیشتری تقسیم کنید:

project/
├── apps/
├── settings/
│   ├── project
│   │   ├── __init__.py
│   │   ├── custom_module_foo.py
│   │   ├── custom_module_bar.py
│   │   └── custom_module_xyz.py
│   ├── third_party
│   │   ├── __init__.py
│   │   ├── celery.py
│   │   ├── email.py
│   │   └── rest_framework.py
│   ├── __init__.py
│   └── djano.py
└── manage.py

 

 

# نام گذاری اطلاعات

نامگذاری متغیرها یکی از پیچیده ترین بخش های توسعه است. نامگذاری تنظیمات نیز همینطور است. ما نمی توانیم به جنگو یا برنامه های شخص ثالث اشاره کنیم، اما می توانیم این قوانین ساده را برای تنظیمات سفارشی (پروژه) خود دنبال کنیم:

 

  • به تنظیمات خود اسامی معنادار بدهید.
  • همیشه از پیشوند با نام پروژه برای تنظیمات سفارشی خود استفاده کنید.
  • توضیحاتی را برای تنظیمات خود در کامنت بنویسید.

 

کد زیر یک مثال بد است:

API_SYNC_CRONTAB = env.str('API_SYNC_CRONTAB')

 

کد زیر یک مثال خوب است:

# Run job for getting new tweets.
# Accept string in crontab format. By default: every 30 minutes.
MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB = env.str(
    'MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB', default='30 * * * *'
)

 

کلمه MYAWESOMEPROJECT را با نام پروژه خود تعویض کنید.

 

 

# جمع بندی

فایل تنظیمات بخش کوچک اما بسیار مهمی از هر پروژه جنگو است. اگر اشتباه انجام دهید، در تمام مراحل توسعه با مشکلات زیادی روبرو خواهید شد. اما اگر این کار را به درستی انجام دهید، پایه خوبی برای پروژه شما خواهد بود که به آن اجازه می دهد در آینده رشد کرده و گسترش یابد.

 

با استفاده از رویکرد متغیرهای محیطی، می‌توانید به راحتی از معماری یکپارچه به معماری میکروسرویس تغییر دهید، پروژه خود را در کانتینرهای Docker بپیچید و آن را در هر پلتفرم میزبانی VPS یا Cloud مانند: Amazon، Google Cloud یا خوشه Kubernetes خود مستقر کنید.

مطالب مشابه



مونگارد