ปรับสภาพแวดล้อมของคุณให้เหมาะสมสำหรับการพัฒนาและการผลิต: บทช่วยสอน Pydantic ตอนที่ 2

เผยแพร่แล้ว: 2022-07-22

นักพัฒนาสามารถเป็นศัตรูตัวฉกาจของตนเองได้ ฉันได้เห็นตัวอย่างวิศวกรจำนวนนับไม่ถ้วนที่พัฒนาระบบที่ไม่เข้ากับสภาพแวดล้อมในการผลิต ความไม่ลงรอยกันนี้นำไปสู่การทำงานพิเศษและไม่จับข้อผิดพลาดของระบบจนกว่าจะถึงขั้นตอนการพัฒนาในภายหลัง การจัดแนวการตั้งค่าเหล่านี้จะทำให้การปรับใช้อย่างต่อเนื่องในท้ายที่สุดง่ายขึ้น เมื่อคำนึงถึงสิ่งนี้ เราจะสร้างแอปพลิเคชันตัวอย่างบนสภาพแวดล้อมการพัฒนา Django ของเรา ทำให้ง่ายขึ้นผ่าน Docker, pydantic และ conda

สภาพแวดล้อมการพัฒนาทั่วไปใช้:

  • ที่เก็บในเครื่อง;
  • ฐานข้อมูล PostgreSQL ที่ใช้ Docker; และ
  • สภาพแวดล้อม conda (เพื่อจัดการการพึ่งพา Python)

Pydantic และ Django เหมาะสำหรับโครงการทั้งแบบเรียบง่ายและซับซ้อน ขั้นตอนต่อไปนี้แสดงวิธีแก้ปัญหาง่ายๆ ที่เน้นถึงวิธีการจำลองสภาพแวดล้อมของเรา

การกำหนดค่าที่เก็บ Git

ก่อนที่เราจะเริ่มเขียนโค้ดหรือติดตั้งระบบการพัฒนา มาสร้างที่เก็บ Git ในเครื่องกันก่อน:

 mkdir hello-visitor cd hello-visitor git init

เราจะเริ่มต้นด้วยไฟล์ Python .gitignore พื้นฐานในรูทของที่เก็บ ตลอดบทแนะนำนี้ เราจะเพิ่มไฟล์นี้ก่อนที่จะเพิ่มไฟล์ที่เราไม่ต้องการให้ Git ติดตาม

การกำหนดค่า Django PostgreSQL โดยใช้ Docker

Django ต้องการฐานข้อมูลเชิงสัมพันธ์และโดยค่าเริ่มต้นจะใช้ SQLite โดยปกติเราจะหลีกเลี่ยง SQLite สำหรับการจัดเก็บข้อมูลที่สำคัญต่อภารกิจ เนื่องจากไม่สามารถจัดการการเข้าถึงของผู้ใช้พร้อมกันได้ดี นักพัฒนาส่วนใหญ่เลือกใช้ฐานข้อมูลที่ใช้งานจริง เช่น PostgreSQL อย่างไรก็ตาม เราควรใช้ฐานข้อมูลเดียวกันสำหรับการพัฒนาและการผลิต อาณัติสถาปัตยกรรมนี้เป็นส่วนหนึ่งของ The Twelve-factor App

โชคดีที่การใช้งานอินสแตนซ์ PostgreSQL ในพื้นที่ด้วย Docker และ Docker Compose นั้นง่ายมาก

เพื่อหลีกเลี่ยงการสร้างมลพิษให้กับไดเรกทอรีรากของเรา เราจะใส่ไฟล์ที่เกี่ยวข้องกับ Docker ไว้ในไดเรกทอรีย่อยที่แยกจากกัน เราจะเริ่มต้นด้วยการสร้างไฟล์ Docker Compose เพื่อปรับใช้ PostgreSQL:

 # docker-services/docker-compose.yml version: "3.9" services: db: image: "postgres:13.4" env_file: .env volumes: - hello-visitor-postgres:/var/lib/postgresql/data ports: - ${POSTGRES_PORT}:5432 volumes: hello-visitor-postgres:

ต่อไป เราจะสร้างไฟล์สภาพแวดล้อม docker-compose เพื่อกำหนดค่าคอนเทนเนอร์ PostgreSQL ของเรา:

 # docker-services/.env POSTGRES_USER=postgres POSTGRES_PASSWORD=MyDBPassword123 # The 'maintenance' database POSTGRES_DB=postgres # The port exposed to localhost POSTGRES_PORT=5432

ขณะนี้เซิร์ฟเวอร์ฐานข้อมูลถูกกำหนดและกำหนดค่าแล้ว มาเริ่มคอนเทนเนอร์ของเราในเบื้องหลังกัน:

 sudo docker compose --project-directory docker-services/ up -d

สิ่งสำคัญคือต้องสังเกตการใช้ sudo ในคำสั่งก่อนหน้า จำเป็นเว้นแต่จะปฏิบัติตามขั้นตอนเฉพาะในสภาพแวดล้อมการพัฒนาของเรา

การสร้างฐานข้อมูล

มาเชื่อมต่อและกำหนดค่า PostgreSQL โดยใช้ชุดเครื่องมือมาตรฐาน pgAdmin4 เราจะใช้ข้อมูลรับรองการเข้าสู่ระบบเดียวกันกับที่กำหนดค่าไว้ก่อนหน้านี้ในตัวแปรสภาพแวดล้อม

ตอนนี้เรามาสร้างฐานข้อมูลใหม่ชื่อ hello_visitor :

หน้าจอ pgAdmin4 ภายในเบราว์เซอร์ที่แสดงแท็บทั่วไปในกล่องโต้ตอบสร้างฐานข้อมูล ฟิลด์ข้อความฐานข้อมูลมีค่า hello_visitor ฟิลด์เจ้าของแสดงผู้ใช้ postgres และฟิลด์ความคิดเห็นว่างเปล่า

ด้วยฐานข้อมูลของเรา เราพร้อมที่จะติดตั้งสภาพแวดล้อมการเขียนโปรแกรมของเรา

การจัดการสภาพแวดล้อม Python ผ่าน Miniconda

ตอนนี้เราจำเป็นต้องตั้งค่าสภาพแวดล้อม Python ที่แยกออกมาและการพึ่งพาที่จำเป็น เพื่อความง่ายในการติดตั้งและบำรุงรักษา เราเลือก Miniconda

มาสร้างและเปิดใช้งานสภาพแวดล้อม conda ของเรา:

 conda create --name hello-visitor python=3.9 conda activate hello-visitor

ตอนนี้ เราจะสร้างไฟล์ hello-visitor/requirements.txt โดยระบุการพึ่งพา Python ของเรา:

 django # PostgreSQL database adapter: psycopg2 # Pushes .env key-value pairs into environment variables: python-dotenv pydantic # Utility library to read database connection information: dj-database-url # Static file caching: whitenoise # Python WSGI HTTP Server: gunicorn

ต่อไป เราจะขอให้ Python ติดตั้งการพึ่งพาเหล่านี้:

 cd hello-visitor pip install -r requirements.txt

ขณะนี้การพึ่งพาของเราควรได้รับการติดตั้งเพื่อเตรียมพร้อมสำหรับงานพัฒนาแอปพลิเคชัน

Django นั่งร้าน

เราจะสร้างโครงงานและแอปของเราโดยเรียกใช้ django-admin ก่อน จากนั้นจึงเรียกใช้ไฟล์ที่สร้างขึ้น manage.py :

 # From the `hello-visitor` directory mkdir src cd src # Generate starter code for our Django project. django-admin startproject hello_visitor . # Generate starter code for our Django app. python manage.py startapp homepage

ต่อไป เราต้องกำหนดค่า Django เพื่อโหลดโครงการของเรา ไฟล์ settings.py ต้องมีการปรับเปลี่ยนอาร์เรย์ INSTALLED_APPS เพื่อลงทะเบียนแอป homepage ที่สร้างขึ้นใหม่ของเรา:

 # src/hello_visitor/settings.py # ... INSTALLED_APPS = [ "homepage.apps.HomepageConfig", "django.contrib.admin", # ... ] # ...

การกำหนดค่าการตั้งค่าแอปพลิเคชัน

โดยใช้วิธีการตั้งค่า pydantic และ Django ที่แสดงในงวดแรก เราจำเป็นต้องสร้างไฟล์ตัวแปรสภาพแวดล้อมสำหรับระบบการพัฒนาของเรา เราจะย้ายการตั้งค่าปัจจุบันของเราไปยังไฟล์นี้ดังนี้:

  1. สร้างไฟล์ src/.env เพื่อเก็บการตั้งค่าสภาพแวดล้อมการพัฒนาของเรา
  2. คัดลอกการตั้งค่าจาก src/hello_visitor/settings.py และเพิ่มลงใน src/.env
  3. ลบบรรทัดที่คัดลอกเหล่านั้นออกจากไฟล์ settings.py
  4. ตรวจสอบให้แน่ใจว่าสตริงการเชื่อมต่อฐานข้อมูลใช้ข้อมูลประจำตัวเดียวกันกับที่เรากำหนดค่าไว้ก่อนหน้านี้

ไฟล์สภาพแวดล้อมของเรา src/.env ควรมีลักษณะดังนี้:

 DATABASE_URL=postgres://postgres:MyDBPassword123@localhost:5432/hello_visitor DATABASE_SSL=False SECRET_KEY="django-insecure-sackl&7(1hc3+%#*4e=)^q3qiw!hnnui*-^($o8t@2^^qqs=%i" DEBUG=True DEBUG_TEMPLATES=True USE_SSL=False ALLOWED_HOSTS='[ "localhost", "127.0.0.1", "0.0.0.0" ]'

เราจะกำหนดค่า Django ให้อ่านการตั้งค่าจากตัวแปรสภาพแวดล้อมของเราโดยใช้ pydantic ด้วยข้อมูลโค้ดนี้:

 # src/hello_visitor/settings.py import os from pathlib import Path from pydantic import ( BaseSettings, PostgresDsn, EmailStr, HttpUrl, ) import dj_database_url # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent class SettingsFromEnvironment(BaseSettings): """Defines environment variables with their types and optional defaults""" # PostgreSQL DATABASE_URL: PostgresDsn DATABASE_SSL: bool = True # Django SECRET_KEY: str DEBUG: bool = False DEBUG_TEMPLATES: bool = False USE_SSL: bool = False ALLOWED_HOSTS: list class Config: """Defines configuration for pydantic environment loading""" env_file = str(BASE_DIR / ".env") case_sensitive = True config = SettingsFromEnvironment() os.environ["DATABASE_URL"] = config.DATABASE_URL DATABASES = { "default": dj_database_url.config(conn_max_age=600, ssl_require=config.DATABASE_SSL) } SECRET_KEY = config.SECRET_KEY DEBUG = config.DEBUG DEBUG_TEMPLATES = config.DEBUG_TEMPLATES USE_SSL = config.USE_SSL ALLOWED_HOSTS = config.ALLOWED_HOSTS # ...

หากคุณพบปัญหาใด ๆ หลังจากแก้ไขก่อนหน้านี้เสร็จแล้ว ให้เปรียบเทียบไฟล์ settings.py ที่สร้างขึ้นเองกับเวอร์ชันในที่เก็บซอร์สโค้ดของเรา

การสร้างแบบจำลอง

แอปพลิเคชันของเราติดตามและแสดงจำนวนผู้เข้าชมโฮมเพจ เราจำเป็นต้องมีโมเดลเพื่อเก็บจำนวนนั้นไว้ จากนั้นจึงใช้ Django's object-relational mapper (ORM) เพื่อเริ่มต้นแถวฐานข้อมูลเดียวผ่านการโยกย้ายข้อมูล

ขั้นแรก เราจะสร้างโมเดล VisitCounter ของเรา:

 # hello-visitor/src/homepage/models.py """Defines the models""" from django.db import models class VisitCounter(models.Model): """ORM for VisitCounter""" count = models.IntegerField() @staticmethod def insert_visit_counter(): """Populates database with one visit counter. Call from a data migration.""" visit_counter = VisitCounter(count=0) visit_counter.save() def __str__(self): return f"VisitCounter - number of visits: {self.count}"

ต่อไป เราจะทริกเกอร์การย้ายข้อมูลเพื่อสร้างตารางฐานข้อมูลของเรา:

 # in the `src` folder python manage.py makemigrations python manage.py migrate

เพื่อตรวจสอบว่ามีตาราง homepage_visitcounter อยู่หรือไม่ เราสามารถดูฐานข้อมูลใน pgAdmin4

ต่อไป เราต้องใส่ค่าเริ่มต้นในตาราง homepage_visitcounter ของเรา มาสร้างไฟล์การโยกย้ายแยกต่างหากเพื่อทำสิ่งนี้โดยใช้ Django scaffolding:

 # from the 'src' directory python manage.py makemigrations --empty homepage

เราจะปรับไฟล์การโยกย้ายที่สร้างขึ้นเพื่อใช้เมธอด VisitCounter.insert_visit_counter ที่เรากำหนดไว้ที่ตอนต้นของส่วนนี้:

 # src/homepage/migrations/0002_auto_-------_----.py # Note: The dashes are dependent on execution time. from django.db import migrations from ..models import VisitCounter def insert_default_items(apps, _schema_editor): """Populates database with one visit counter.""" # To learn about apps, see: # https://docs.djangoproject.com/en/3.2/topics/migrations/#data-migrations VisitCounter.insert_visit_counter() class Migration(migrations.Migration): """Runs a data migration.""" dependencies = [ ("homepage", "0001_initial"), ] operations = [ migrations.RunPython(insert_default_items), ]

ตอนนี้เราพร้อมที่จะดำเนินการโยกย้ายที่แก้ไขแล้วสำหรับแอป homepage :

 # from the 'src' directory python manage.py migrate homepage

มาตรวจสอบว่าการโยกย้ายดำเนินการอย่างถูกต้องโดยดูที่เนื้อหาของตารางของเรา:

หน้าจอ pgAdmin4 ภายในเบราว์เซอร์แสดงข้อความค้นหา "SELECT * FROM public.homepage_visitcounter ORDER BY id ASC" แท็บ Data Output แสดงว่ามีหนึ่งแถวในตารางนั้น ค่าฟิลด์รหัสคีย์ตัวแทนคือ 1 และค่าฟิลด์การนับคือ 0

เราเห็นว่าตาราง homepage_visitcounter ของเรามีอยู่แล้วและได้รับการเติมด้วยจำนวนการเข้าชมเริ่มต้นที่ 0 เมื่อฐานข้อมูลของเราถูกยกกำลังสอง เราจะมุ่งเน้นไปที่การสร้าง UI ของเรา

สร้างและกำหนดค่ามุมมองของเรา

เราจำเป็นต้องใช้สองส่วนหลักใน UI ของเรา: มุมมองและเทมเพลต

เราสร้างมุมมอง homepage เพื่อเพิ่มจำนวนผู้เข้าชม บันทึกลงในฐานข้อมูล และส่งจำนวนนั้นไปยังเทมเพลตสำหรับแสดง:

 # src/homepage/views.py from django.shortcuts import get_object_or_404, render from .models import VisitCounter def index(request): """View for the main page of the app.""" visit_counter = get_object_or_404(VisitCounter, pk=1) visit_counter.count += 1 visit_counter.save() context = {"visit_counter": visit_counter} return render(request, "homepage/index.html", context)

แอปพลิเคชัน Django ของเราต้องฟังคำขอที่มุ่งเป้าไปที่ homepage ในการกำหนดการตั้งค่านี้ เราจะเพิ่มไฟล์นี้:

 # src/homepage/urls.py """Defines urls""" from django.urls import path from . import views # The namespace of the apps' URLconf app_name = "homepage" # pylint: disable=invalid-name urlpatterns = [ path("", views.index, name="index"), ]

เพื่อให้แอปพลิเคชัน homepage ของเราใช้งานได้ เราต้องลงทะเบียนในไฟล์ urls.py อื่น:

 # src/hello_visitor/urls.py from django.contrib import admin from django.urls import include, path urlpatterns = [ path("", include("homepage.urls")), path("admin/", admin.site.urls), ]

เทมเพลต HTML พื้นฐานของโครงการของเราจะอยู่ในไฟล์ใหม่ src/templates/layouts/base.html :

 <!DOCTYPE html> {% load static %} <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <title>Hello, visitor!</title> <link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}"/> </head> <body> {% block main %}{% endblock %} <!-- Option 1: Bootstrap Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>

เราจะขยายเทมเพลตพื้นฐานสำหรับแอป homepage ของเราในไฟล์ใหม่ src/templates/homepage/index.html :

 {% extends "layouts/base.html" %} {% block main %} <main> <div class="container py-4"> <div class="p-5 mb-4 bg-dark text-white text-center rounded-3"> <div class="container-fluid py-5"> <h1 class="display-5 fw-bold">Hello, visitor {{ visit_counter.count }}!</h1> </div> </div> </div> </main> {% endblock %}

ขั้นตอนสุดท้ายในการสร้าง UI ของเราคือบอก Django ว่าจะหาเทมเพลตเหล่านี้ได้ที่ไหน มาเพิ่มรายการพจนานุกรม TEMPLATES['DIRS'] ให้กับไฟล์ settings.py ของเรา:

 # src/hello_visitor/settings.py TEMPLATES = [ { ... 'DIRS': [BASE_DIR / 'templates'], ... }, ]

อินเทอร์เฟซผู้ใช้ของเราได้รับการใช้งานแล้ว และเราเกือบจะพร้อมที่จะทดสอบการทำงานของแอปพลิเคชันของเราแล้ว ก่อนที่เราจะทำการทดสอบ เราต้องใส่ส่วนสุดท้ายของสภาพแวดล้อมของเรา: การแคชเนื้อหาแบบคงที่

การกำหนดค่าเนื้อหาแบบคงที่ของเรา

เพื่อหลีกเลี่ยงการใช้ทางลัดทางสถาปัตยกรรมในระบบการพัฒนาของเรา เราจะกำหนดค่าการแคชเนื้อหาแบบคงที่เพื่อจำลองสภาพแวดล้อมการผลิตของเรา

เราจะเก็บไฟล์สแตติกของโปรเจ็กต์ทั้งหมดไว้ในไดเร็กทอรีเดียว src/static และสั่งให้ Django รวบรวมไฟล์เหล่านั้นก่อนนำไปใช้งาน

เราจะใช้โลโก้ของ Toptal สำหรับ favicon ของแอปพลิเคชันของเราและจัดเก็บเป็น src/static/favicon.ico :

 # from `src` folder mkdir static cd static wget https://frontier-assets.toptal.com/83b2f6e0d02cdb3d951a75bd07ee4058.png mv 83b2f6e0d02cdb3d951a75bd07ee4058.png favicon.ico

ต่อไป เราจะกำหนดค่า Django เพื่อรวบรวมไฟล์สแตติก:

 # src/hello_visitor/settings.py # Static files (CSS, JavaScript, images) # a la https://docs.djangoproject.com/en/3.2/howto/static-files/ # # Source location where we'll store our static files STATICFILES_DIRS = [BASE_DIR / "static"] # Build output location where Django collects all static files STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_ROOT.mkdir(exist_ok=True) # URL to use when referring to static files located in STATIC_ROOT. STATIC_URL = "/static/" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"

เราต้องการเก็บไฟล์สแตติกดั้งเดิมของเราไว้ในที่เก็บซอร์สโค้ดเท่านั้น เราไม่ต้องการจัดเก็บเวอร์ชันที่ปรับให้เหมาะกับการใช้งานจริง มาเพิ่มส่วนหลังใน .gitignore ของเราด้วยบรรทัดง่ายๆ นี้:

 staticfiles

ด้วยที่เก็บซอร์สโค้ดของเราจัดเก็บไฟล์ที่จำเป็นอย่างถูกต้อง ตอนนี้เราจำเป็นต้องกำหนดค่าระบบแคชของเราให้ทำงานกับไฟล์สแตติกเหล่านี้

การแคชไฟล์แบบคงที่

ในการผลิตและในสภาพแวดล้อมการพัฒนาของเรา เราจะใช้ WhiteNoise เพื่อให้บริการไฟล์สแตติกของแอปพลิเคชัน Django ของเราอย่างมีประสิทธิภาพมากขึ้น

เราลงทะเบียน WhiteNoise เป็นมิดเดิลแวร์โดยเพิ่มข้อมูลโค้ดต่อไปนี้ในไฟล์ src/hello_visitor/settings.py ของเรา ลำดับการลงทะเบียนถูกกำหนดอย่างเข้มงวด และ WhiteNoiseMiddleware ต้องปรากฏขึ้นทันทีหลังจาก SecurityMiddleware :

 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', # ... ] STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

การแคชไฟล์แบบคงที่ควรได้รับการกำหนดค่าในสภาพแวดล้อมการพัฒนาของเรา ซึ่งช่วยให้เราเรียกใช้แอปพลิเคชันของเราได้

เรียกใช้เซิร์ฟเวอร์การพัฒนาของเรา

เรามีแอปพลิเคชั่นที่เข้ารหัสอย่างสมบูรณ์และตอนนี้สามารถเปิดเว็บเซิร์ฟเวอร์การพัฒนาที่ฝังตัวของ Django ด้วยคำสั่งนี้:

 # in the `src` folder python manage.py runserver

เมื่อเราไปที่ http://localhost:8000 จำนวนจะเพิ่มขึ้นทุกครั้งที่เรารีเฟรชหน้า:

หน้าต่างเบราว์เซอร์ที่แสดงหน้าจอหลักของแอปพลิเคชัน pydantic Django ของเรา ซึ่งระบุว่า "สวัสดี ผู้เยี่ยมชม!" ในหนึ่งบรรทัดและ "1" ในบรรทัดถัดไป

ขณะนี้เรามีแอปพลิเคชันที่ใช้งานได้ซึ่งจะเพิ่มจำนวนการเข้าชมเมื่อเรารีเฟรชหน้า

พร้อมที่จะปรับใช้

บทช่วยสอนนี้ครอบคลุมขั้นตอนทั้งหมดที่จำเป็นในการสร้างแอปที่ใช้งานได้ในสภาพแวดล้อมการพัฒนา Django ที่สวยงามซึ่งตรงกับการผลิต ในส่วนที่ 3 เราจะพูดถึงการปรับใช้แอปพลิเคชันของเรากับสภาพแวดล้อมการผลิต นอกจากนี้ยังควรค่าแก่การสำรวจแบบฝึกหัดเพิ่มเติมของเราที่เน้นถึงประโยชน์ของ Django และ pydantic: ซึ่งรวมอยู่ในที่เก็บโค้ดที่สมบูรณ์สำหรับบทช่วยสอน pydantic นี้


บล็อก Toptal Engineering ขอขอบคุณ Stephen Davidson สำหรับการตรวจสอบและทดสอบเบต้าตัวอย่างโค้ดที่นำเสนอในบทความนี้