انتقال از App Engine Blobstore به «فضای ذخیره‌سازی ابری» (واحد ۱۶)

۱. نمای کلی

هدف از مجموعه codelabهای Serverless Migration Station (آموزش‌های عملی با سرعت پیشروی دلخواه) و ویدیوهای مرتبط کمک به توسعه‌دهندگان Google Cloud serverless برای مدرن‌سازی برنامه‌هایشان ازطریق راهنمایی آن‌ها در یک یا چند انتقال، عمدتاً با فاصله گرفتن از سرویس‌های قدیمی، است. این کار باعث می‌شود برنامه‌هایتان قابل‌حمل‌تر شوند و گزینه‌ها و انعطاف‌پذیری بیشتری به شما می‌دهد، و به شما امکان می‌دهد با طیف وسیع‌تری از محصولات Cloud ادغام شوید و به آن‌ها دسترسی داشته باشید و راحت‌تر به نسخه‌های جدیدتر زبان ارتقا دهید. درحالی‌که این مجموعه در ابتدا بر اولین کاربران Cloud، عمدتاً توسعه‌دهندگان App Engine (محیط استاندارد)، تمرکز دارد، به‌اندازه کافی گسترده است که شامل پلاتفرم‌های بدون سرور دیگر مانند Cloud Functions و Cloud Run، یا جاهای دیگر درصورت لزوم، شود.

این codelab به شما آموزش می‌دهد که چگونه از App Engine Blobstore به فضای ذخیره‌سازی ابری منتقل شوید. همچنین انتقال‌های ضمنی از موارد زیر وجود دارد:

برای اطلاعات گام‌به‌گام بیشتر، به هرکدام از ماژول‌های انتقال مربوطه مراجعه کنید.

با روش انجام این کارها آشنا خواهید شد

  • افزودن استفاده از کتابخانه/ میانای برنامه‌سازی کاربردی App Engine Blobstore
  • ذخیره کردن بارگذاری‌های کاربر در سرویس Blobstore
  • برای مرحله بعدی انتقال به Cloud Storage آماده شوید

آنچه نیاز خواهید داشت

نظرسنجی

چگونه از این آموزش استفاده خواهید کرد؟

فقط آن را بخوانید آن را بخوانید و تمرین‌ها را تکمیل کنید

تجربه‌تان را با Python چگونه رده‌بندی می‌کنید؟

مبتدی متوسط ماهر

به تجربه استفاده از سرویس‌های Google Cloud چه امتیازی می‌دهید؟

مبتدی متوسط ماهر

۲. پس‌زمینه

این codelab با برنامه نمونه از «واحد ۱۵» شروع می‌شود و نحوه انتقال از Blobstore (و NDB) به «فضای ذخیره‌سازی ابری» (و Cloud NDB) را نشان می‌دهد. فرایند انتقال شامل جایگزین کردن وابستگی‌ها به سرویس‌های بسته‌ای قدیمی App Engine است که به شما امکان می‌دهد درصورت تمایل برنامه‌هایتان را به پلاتفرم بدون سرور Cloud دیگری یا پلاتفرم میزبانی دیگری منتقل کنید.

این انتقال در مقایسه با سایر انتقال‌ها در این مجموعه به تلاش بیشتری نیاز دارد. ‫Blobstore به چارچوب وب‌برنامه اصلی وابسته است و به همین دلیل است که برنامه نمونه به‌جای Flask از چارچوب webapp2 استفاده می‌کند. این آموزش گام‌به‌گام شامل انتقال به Cloud Storage،‏ Cloud NDB،‏ Flask، و Python 3 است.

برنامه همچنان «بازدیدهای» کاربر نهایی را ثبت می‌کند و ده بازدید اخیر را نمایش می‌دهد، اما کدلَب قبلی (واحد ۱۵) عملکرد جدیدی را برای تطبیق با استفاده از Blobstore اضافه کرد: برنامه از کاربران نهایی می‌خواهد تا یک آرتیفکت (فایل) مربوط به «بازدید» خود را بارگذاری کنند. کاربران می‌توانند این کار را انجام دهند یا با انتخاب «رد شدن» انصراف دهند. صرف‌نظر از تصمیم کاربر، صفحه بعدی همان خروجی نسخه‌های قبلی این برنامه را ارائه می‌دهد و جدیدترین بازدیدها را نمایش می‌دهد. یک پیچیدگی دیگر این است که بازدیدهای دارای آرتیفکت‌های مربوطه، پیوند «مشاهده» برای نمایش آرتیفکت بازدید دارند. این codelab انتقال‌های ذکرشده در بالا را پیاده‌سازی می‌کند و درعین‌حال عملکرد شرح‌داده‌شده را حفظ می‌کند.

۳. راه‌اندازی/ پیش‌کار

قبل‌از اینکه به بخش اصلی آموزش برسیم، بیایید پروژه‌مان را راه‌اندازی کنیم، کد را دریافت کنیم، سپس برنامه پایه را مستقر کنیم تا مطمئن شویم که با کد فعال شروع کرده‌ایم.

۱. راه‌اندازی پروژه

اگر قبلاً برنامه «واحد ۱۵» را مستقر کرده‌اید، توصیه می‌کنیم از همان پروژه (و کد) دوباره استفاده کنید. به‌علاوه، می‌توانید پروژه کاملاً جدیدی ایجاد کنید یا از پروژه موجود دیگری دوباره استفاده کنید. مطمئن شوید پروژه حساب صورت‌حساب فعال داشته باشد و App Engine فعال باشد.

۲. دریافت برنامه نمونه پایه

یکی از پیش‌نیازهای این codelab داشتن برنامه نمونه «واحد ۱۵» است که کار کند. اگر آن را ندارید، می‌توانید آن را از پوشه «شروع» «واحد ۱۵» (پیوند زیر) دریافت کنید. این codelab شما را در هر مرحله راهنمایی می‌کند و با کدی که شبیه آنچه در پوشه «FINISH» ماژول ۱۶ است به پایان می‌رسد.

دایرکتوری فایل‌های STARTing «واحد ۱۵» باید به این شکل باشد:

$ ls
README.md       app.yaml        main-gcs.py     main.py         templates

فایل main-gcs.py نسخه جایگزین main.py از «واحد ۱۵» است که امکان انتخاب یک دسته Cloud Storage متفاوت از نشانی وب پیش‌فرض اختصاص‌داده‌شده به برنامه را براساس شناسه پروژه فراهم می‌کند: PROJECT_ID.appspot.com. این فایل نقشی در این codelab (واحد ۱۶) ندارد، جز اینکه درصورت تمایل می‌توان از تکنیک‌های انتقال مشابه برای آن فایل استفاده کرد.

۳. استقرار (مجدد) برنامه مبنا

مراحل پیش‌کار باقی‌مانده شما برای اجرا درحال‌حاضر:

  1. دوباره با ابزار خط فرمان gcloud آشنا شوید
  2. برنامه نمونه را با gcloud app deploy دوباره مستقر کنید
  3. تأیید کنید که برنامه بدون مشکل در App Engine اجرا می‌شود

وقتی این مراحل را با موفقیت اجرا کردید و تأیید کردید که برنامه «واحد ۱۵» شما کار می‌کند. صفحه اولیه با فرمی به کاربران خوشامد می‌گوید که از آن‌ها می‌خواهد فایل مصنوع بازدید را برای بارگذاری وارد کنند و گزینه‌ای هم برای انصراف دارد (دکمه «رد شدن»):

f5b5f9f19d8ae978.png

وقتی کاربران فایلی را بارگذاری می‌کنند یا از آن رد می‌شوند، برنامه صفحه آشنای «جدیدترین بازدیدها» را ارائه می‌دهد:

f5ac6b98ee8a34cb.png

بازدیدهایی که حاوی مصنوع هستند، پیوند «مشاهده» در سمت راست مُهر زمان بازدید خواهند داشت تا مصنوع را نمایش دهند (یا بارگیری کنند). پس‌از تأیید عملکرد برنامه، آماده‌اید تا از سرویس‌های قدیمی App Engine (‏webapp2،‏ NDB،‏ Blobstore) به جایگزین‌های معاصر (Flask،‏ Cloud NDB،‏ «فضای ذخیره‌سازی ابری») انتقال دهید.

۴. به‌روزرسانی فایل‌های پیکربندی

سه فایل پیکربندی برای نسخه به‌روزشده برنامه ما وارد بازی می‌شوند. کارهای لازم عبارت‌اند از:

  1. کتابخانه‌های شخص ثالث داخلی موردنیاز را در app.yaml به‌روز کنید و همچنین امکان انتقال به Python 3 را فراهم کنید
  2. یک requirements.txt اضافه کنید که همه کتابخانه‌های موردنیاز را که داخلی نیستند مشخص کند
  3. appengine_config.py را اضافه کنید تا برنامه از کتابخانه‌های شخص ثالث داخلی و غیرداخلی پشتیبانی کند

app.yaml

فایل app.yaml را با به‌روزرسانی بخش libraries ویرایش کنید. jinja2 را بردارید و grpcio، setuptools، و ssl را اضافه کنید. جدیدترین نسخه دردسترس را برای هر سه کتابخانه انتخاب کنید. همچنین دستورالعمل Python 3 runtime را اضافه کنید، اما آن را به‌صورت نظر غیرفعال کنید. وقتی کارتان تمام شد، باید این‌گونه به‌نظر برسد (اگر Python 3.9 را انتخاب کرده باشید):

قبل:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: jinja2
  version: latest

بعد:

#runtime: python39
runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest
- name: ssl
  version: latest

تغییرات عمدتاً مربوط به کتابخانه‌های داخلی Python 2 موجود در سرورهای App Engine است (بنابراین لازم نیست آن‌ها را خودتان بسته‌بندی کنید). Jinja2 را برداشتیم چون با Flask ارائه می‌شود که قرار است آن را به reqs.txt اضافه کنیم. هرگاه از کتابخانه‌های مشتری Google Cloud، مثل کتابخانه‌های Cloud NDB و Cloud Storage، استفاده شود، grpcio و setuptools لازم است. درنهایت، خود Cloud Storage به کتابخانه ssl نیاز دارد. دستور زمان اجرای غیرفعال‌شده در بالا برای زمانی است که آماده‌اید این برنامه را به Python 3 منتقل کنید. در پایان این آموزش گام‌به‌گام به این موضوع خواهیم پرداخت.

requirements.txt

یک فایل requirements.txt اضافه کنید که به چارچوب Flask و کتابخانه‌های کارخواه Cloud NDB و Cloud Storage نیاز دارد، هیچ‌کدام از این‌ها داخلی نیستند. فایل را با این محتوا بسازید:

flask
google-cloud-ndb
google-cloud-storage

زمان اجرای Python 2 App Engine به بسته‌بندی خودکار کتابخانه‌های شخص ثالث غیرداخلی نیاز دارد، بنابراین فرمان زیر را برای نصب این کتابخانه‌ها در پوشه lib اجرا کنید:

pip install -t lib -r requirements.txt

اگر Python 2 و 3 را در ماشین توسعه نرم‌افزار خود دارید، ممکن است لازم باشد از فرمان pip2 استفاده کنید تا مطمئن شوید نسخه‌های Python 2 این کتابخانه‌ها را دریافت می‌کنید. وقتی به Python 3 ارتقا دهید، دیگر نیازی به بسته‌بندی خودکار ندارید.

appengine_config.py

فایل appengine_config.py را که از کتابخانه‌های شخص ثالث داخلی و غیرداخلی پشتیبانی می‌کند اضافه کنید. فایل را با این محتوا بسازید:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

مراحلی که به‌تازگی تکمیل شد باید مشابه یا یکسان با مراحل فهرست‌شده در بخش نصب کتابخانه‌ها برای برنامه‌های Python 2 در اسناد App Engine باشد، و به‌طور خاص، محتوای appengine_config.py باید با آنچه در مرحله ۵ آنجا آمده است مطابقت داشته باشد.

کار روی فایل‌های پیکربندی تکمیل شده است، پس بیایید به برنامه برویم.

۵. اصلاح فایل‌های برنامه

واردات

اولین مجموعه تغییرات برای main.py شامل تعویض همه چیزهایی است که جایگزین می‌شوند. موارد زیر تغییر می‌کند:

  1. ‫webapp2 با Flask جایگزین شده است
  2. به‌جای استفاده از Jinja2 از webapp2_extras، از Jinja2 که با Flask ارائه می‌شود استفاده کنید
  3. ‫App Engine Blobstore و NDB با Cloud NDB و «فضای ذخیره‌سازی ابری» جایگزین شده‌اند
  4. گرداننده‌های Blobstore در webapp با ترکیبی از واحد کتابخانه استاندارد io،‏ Flask، و برنامه‌های کمکی werkzeug جایگزین شده‌اند
  5. به‌طور پیش‌فرض، Blobstore در یک فضای ذخیره‌سازی Cloud Storage که نام آن از نشانی وب برنامه شما (PROJECT_ID.appspot.com) گرفته شده است، می‌نویسد. ازآنجایی‌که ما درحال انتقال به کتابخانه مشتری Cloud Storage هستیم، از google.auth برای دریافت شناسه پروژه جهت مشخص کردن دقیقاً همان نام فضای ذخیره‌سازی استفاده می‌شود. (می‌توانید نام سطل را تغییر دهید چون دیگر کدنویسی ثابت نیست.)

قبل:

import webapp2
from webapp2_extras import jinja2
from google.appengine.ext import blobstore, ndb
from google.appengine.ext.webapp import blobstore_handlers

تغییرات فهرست بالا را با جایگزین کردن بخش وارد کردن فعلی در main.py با تکه‌کد زیر اعمال کنید.

بعد:

import io

from flask import (Flask, abort, redirect, render_template,
        request, send_file, url_for)
from werkzeug.utils import secure_filename

import google.auth
from google.cloud import exceptions, ndb, storage

راه‌اندازی و پشتیبانی غیرضروری Jinja2

بلوک کد بعدی برای جایگزینی BaseHandler است که استفاده از Jinja2 را از webapp2_extras مشخص می‌کند. این کار لازم نیست چون Jinja2 با Flask ارائه می‌شود و موتور قالب‌بندی پیش‌فرض آن است، بنابراین آن را بردارید.

در بخش «واحد ۱۶»، اشیایی را که در برنامه قدیمی‌تر نداشتیم نمونه‌سازی کنید. این شامل مقداردهی اولیه برنامه Flask و ایجاد کارخواه‌های میانای برنامه‌سازی کاربردی برای Cloud NDB و «فضای ذخیره‌سازی ابری» می‌شود. درنهایت، نام مخزن Cloud Storage را همان‌طور که در بالا در بخش واردات شرح داده شد، کنار هم قرار می‌دهیم. در اینجا قبل و بعداز اجرای این به‌روزرسانی‌ها را می‌بینید:

قبل:

class BaseHandler(webapp2.RequestHandler):
    'Derived request handler mixing-in Jinja2 support'
    @webapp2.cached_property
    def jinja2(self):
        return jinja2.get_jinja2(app=self.app)

    def render_response(self, _template, **context):
        self.response.write(self.jinja2.render_template(_template, **context))

بعد:

app = Flask(__name__)
ds_client = ndb.Client()
gcs_client = storage.Client()
_, PROJECT_ID = google.auth.default()
BUCKET = '%s.appspot.com' % PROJECT_ID

به‌روزرسانی دسترسی به Datastore

Cloud NDB تا حد زیادی با App Engine NDB سازگار است. یکی‌از تفاوت‌هایی که قبلاً پوشش داده شده است نیاز به کارخواه میانای برنامه‌سازی کاربردی است. مورد دیگر این است که در دومی، دسترسی به Datastore باید توسط مدیر زمینه Python کارخواه API کنترل شود. اساساً این یعنی همه تماس‌های دسترسی Datastore که از کتابخانه کارخواه Cloud NDB استفاده می‌کنند فقط می‌توانند در بلوک‌های Python with رخ دهند.

این یک تغییر است؛ تغییر دیگر این است که Blobstore و اشیای آن، مثلاً BlobKey، توسط Cloud Storage پشتیبانی نمی‌شوند، بنابراین file_blob را به ndb.StringProperty تغییر دهید. در زیر کلاس مدل داده و توابع به‌روزشده store_visit() و fetch_visits() که این تغییرات را منعکس می‌کنند آورده شده است:

قبل:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent),
            file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

بعد:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.StringProperty()

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_blob=upload_key).put()

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

در اینجا تصویری از تغییراتی که تاکنون انجام شده است ارائه شده است:

a8f74ca392275822.png

درحال به‌روزرسانی گرداننده‌ها

مدیریت‌كننده بارگذاری

گرداننده‌ها در webapp2 کلاس هستند، درحالی‌که در Flask تابع هستند. به‌جای روش فعل HTTP،‏ Flask از فعل برای تزئین تابع استفاده می‌کند. Blobstore و گرداننده‌های webapp آن با عملکرد Cloud Storage و همچنین Flask و ابزارهای آن جایگزین می‌شوند:

قبل:

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    'Upload blob (POST) handler'
    def post(self):
        uploads = self.get_uploads()
        blob_id = uploads[0].key() if uploads else None
        store_visit(self.request.remote_addr, self.request.user_agent, blob_id)
        self.redirect('/', code=307)

بعد:

@app.route('/upload', methods=['POST'])
def upload():
    'Upload blob (POST) handler'
    fname = None
    upload = request.files.get('file', None)
    if upload:
        fname = secure_filename(upload.filename)
        blob = gcs_client.bucket(BUCKET).blob(fname)
        blob.upload_from_file(upload, content_type=upload.content_type)
    store_visit(request.remote_addr, request.user_agent, fname)
    return redirect(url_for('root'), code=307)

چند نکته درباره این به‌روزرسانی:

  • به‌جای blob_id، مصنوعات فایل اکنون با نام فایل (fname) درصورت وجود شناسایی می‌شوند، و درغیراین‌صورت با None (کاربر از بارگذاری فایل انصراف داده است).
  • گرداننده‌های Blobstore فرایند بارگذاری را از کاربرانش پنهان می‌کردند، اما Cloud Storage این کار را نمی‌کند، بنابراین می‌توانید کد جدیداً اضافه‌شده را ببینید که شیء لکه فایل و مکان (سطل) را تنظیم می‌کند و همچنین فراخوانی‌ای را که بارگذاری واقعی را انجام می‌دهد. (upload_from_file()).
  • webapp2 از جدول مسیریابی در پایین فایل برنامه استفاده می‌کند، درحالی‌که مسیرهای Flask در هر گرداننده تزئین‌شده یافت می‌شوند.
  • هر دو گرداننده عملکرد خود را با هدایت به صفحه اصلی ( / ) و حفظ درخواست POST با کد بازگشت HTTP 307 تکمیل می‌کنند.

مدیریت‌كننده بارگیری

به‌روزرسانی گرداننده بارگیری از الگویی مشابه گرداننده بارگذاری پیروی می‌کند، با این تفاوت که کد بسیار کمتری برای بررسی وجود دارد. عملکرد Blobstore و webapp را با معادل‌های «فضای ذخیره‌سازی ابری» و Flask جایگزین کنید:

قبل:

class ViewBlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
    'view uploaded blob (GET) handler'
    def get(self, blob_key):
        self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)

بعد:

@app.route('/view/<path:fname>')
def view(fname):
    'view uploaded blob (GET) handler'
    blob = gcs_client.bucket(BUCKET).blob(fname)
    try:
        media = blob.download_as_bytes()
    except exceptions.NotFound:
        abort(404)
    return send_file(io.BytesIO(media), mimetype=blob.content_type)

نکاتی درباره این به‌روزرسانی:

  • دوباره، Flask توابع گرداننده را با مسیرشان تزئین می‌کند درحالی‌که webapp این کار را در جدول مسیریابی در پایین انجام می‌دهد، بنابراین نحو تطبیق الگوی دومی ('/view/([^/]+)?') را درمقابل Flask‏ ('/view/<path:fname>') تشخیص دهید.
  • As with the upload handler, there is a little more work required on the Cloud Storage side for functionality abstracted away by the Blobstore handlers, namely identifying the file (blob) in question and explicitly downloading the binary vs. Blobstore handler's single send_blob() method call.
  • در هر دو مورد، اگر آرتیفکت پیدا نشود، خطای HTTP 404 به کاربر برگردانده می‌شود.

کنترل‌کننده اصلی

تغییرات نهایی در برنامه اصلی در رسیدگی‌کننده اصلی انجام می‌شود. روش‌های فعل HTTP webapp2 با یک تابع واحد که عملکرد آن‌ها را ترکیب می‌کند جایگزین شده است. کلاس MainHandler را با تابع root() جایگزین کنید و جدول مسیریابی webapp2 را همان‌طور که در زیر نشان داده شده است بردارید:

قبل:

class MainHandler(BaseHandler):
    'main application (GET/POST) handler'
    def get(self):
        self.render_response('index.html',
                upload_url=blobstore.create_upload_url('/upload'))

    def post(self):
        visits = fetch_visits(10)
        self.render_response('index.html', visits=visits)

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/upload', UploadHandler),
    ('/view/([^/]+)?', ViewBlobHandler),
], debug=True)

بعد:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = fetch_visits(10)
    return render_template('index.html', **context)

به‌جای روش‌های جداگانه get() و post()، آن‌ها اساساً یک عبارت if-else در root() هستند. همچنین، چون root() یک تابع واحد است، فقط یک فراخوانی برای ارائه الگو برای هر دو GET و POST وجود دارد، درحالی‌که این کار در webapp2 واقعاً ممکن نیست.

در اینجا نمایش تصویری این مجموعه دوم و نهایی تغییرات در main.py آمده است:

5ec38818c32fec2.png

(اختیاری) «بهبود» سازگاری با نسخه قدیمی

بنابراین راه‌حل ایجادشده در بالا به‌خوبی کار می‌کند... اما فقط درصورتی‌که از ابتدا شروع کنید و فایل‌های ایجادشده توسط Blobstore را نداشته باشید. چون برنامه را به‌روزرسانی کردیم تا فایل‌ها را به‌جای BlobKey با نام فایل شناسایی کند، برنامه تکمیل‌شده «واحد ۱۶» به‌همین صورتی که هست نمی‌تواند فایل‌های Blobstore را مشاهده کند. به عبارت دیگر، ما تغییری ناسازگار با نسخه‌های قبلی ایجاد کردیم که این انتقال را انجام می‌دهد. اکنون نسخه جایگزینی از main.py به‌نام main-migrate.py (موجود در مخزن) ارائه می‌دهیم که تلاش می‌کند این شکاف را پر کند.

اولین «افزونه» برای پشتیبانی از فایل‌های ایجادشده Blobstore یک مدل داده است که دارای BlobKeyProperty است (علاوه‌بر StringProperty برای فایل‌های ایجادشده Cloud Storage):

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)
    file_blob = ndb.BlobKeyProperty()  # backwards-compatibility
    file_gcs  = ndb.StringProperty()

از ویژگی file_blob برای شناسایی فایل‌های ایجادشده توسط Blobstore استفاده خواهد شد، درحالی‌که file_gcs برای فایل‌های Cloud Storage است. اکنون هنگام ایجاد بازدیدهای جدید، به‌طور صریح مقداری را در file_gcs به‌جای file_blob ذخیره کنید، بنابراین store_visit کمی متفاوت به‌نظر می‌رسد:

قبل:

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_blob=upload_key).put()

بعد:

def store_visit(remote_addr, user_agent, upload_key):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent),
                file_gcs=upload_key).put()

هنگام واکشی جدیدترین بازدیدها، داده‌ها را قبل‌از ارسال به الگو «نرمال‌سازی» کنید:

قبل:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = fetch_visits(10)
    return render_template('index.html', **context)

بعد:

@app.route('/', methods=['GET', 'POST'])
def root():
    'main application (GET/POST) handler'
    context = {}
    if request.method == 'GET':
        context['upload_url'] = url_for('upload')
    else:
        context['visits'] = etl_visits(fetch_visits(10))
    return render_template('index.html', **context)

سپس، وجود file_blob یا file_gcs (یا هیچ‌کدام) را تأیید کن. اگر فایلی دردسترس است، فایلی را که وجود دارد انتخاب کنید و از آن شناسه استفاده کنید (BlobKey برای فایل‌های ایجادشده توسط Blobstore یا نام فایل برای فایل‌های ایجادشده توسط Cloud Storage). وقتی می‌گوییم «فایل‌های ایجادشده توسط Cloud Storage»، منظورمان فایل‌هایی است که بااستفاده از کتابخانه کارخواه Cloud Storage ایجاد شده‌اند. Blobstore در Cloud Storage هم می‌نویسد، اما در این مورد، این فایل‌ها توسط Blobstore ایجاد شده‌اند.

اکنون مهم‌تر از همه، این تابع etl_visits() چیست که برای نرمال‌سازی یا ETL (استخراج، تبدیل، و بارگذاری) داده‌ها برای کاربر نهایی استفاده می‌شود؟ به این شکل است:

def etl_visits(visits):
    return [{
            'visitor': v.visitor,
            'timestamp': v.timestamp,
            'file_blob': v.file_gcs if hasattr(v, 'file_gcs') \
                    and v.file_gcs else v.file_blob
            } for v in visits]

احتمالاً همان چیزی است که انتظار داشتید: کد در همه بازدیدها تکرار می‌شود و برای هر بازدید، داده‌های بازدیدکننده و مُهر زمان را به‌طور دقیق می‌گیرد، سپس بررسی می‌کند که آیا file_gcs یا file_blob وجود دارد یا نه، و اگر وجود داشته باشد، یکی از آن‌ها را انتخاب می‌کند (یا None اگر هیچ‌کدام وجود نداشته باشد).

در اینجا تصویری از تفاوت‌های بین main.py و main-migrate.py آورده شده است:

718b05b2adadb2e1.png

اگر از ابتدا بدون فایل‌های ایجادشده توسط Blobstore شروع می‌کنید، از main.py استفاده کنید، اما اگر درحال انتقال هستید و می‌خواهید از فایل‌های ایجادشده توسط Blobstore و Cloud Storage پشتیبانی کنید، main-migrate.py را به‌عنوان نمونه‌ای از نحوه برخورد با سناریوهایی مانند کمک به شما در برنامه‌ریزی انتقال برای برنامه‌های خود بررسی کنید. هنگام انجام انتقال‌های پیچیده، احتمالاً موارد خاصی پیش می‌آید، بنابراین این مثال برای نشان دادن تمایل بیشتر به مدرن‌سازی برنامه‌های واقعی با داده‌های واقعی است.

‫۶. خلاصه/پاک‌سازی

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

استقرار و درستی‌سنجی برنامه

قبل‌از استقرار مجدد برنامه‌تان، حتماً pip install -t lib -r requirements.txt را اجرا کنید تا کتابخانه‌های طرف سوم خودبسته‌بندی‌شده را در پوشه lib دریافت کنید. اگر می‌خواهید راه‌حل سازگار با نسخه قدیمی را اجرا کنید، ابتدا نام main-migrate.py را به main.py تغییر دهید. اکنون gcloud app deploy را اجرا کنید و تأیید کنید که برنامه دقیقاً مانند برنامه «واحد ۱۵» کار می‌کند. صفحه فرم به این شکل است:

f5b5f9f19d8ae978.png

صفحه جدیدترین بازدیدها این‌گونه است:

f5ac6b98ee8a34cb.png

تبریک می‌گوییم که این codelab را برای جایگزین کردن App Engine Blobstore با «فضای ذخیره‌سازی ابری»، App Engine NDB با Cloud NDB، و webapp2 با Flask تکمیل کردید. اکنون کد شما باید با آنچه در پوشه FINISH (مدول ۱۶) است مطابقت داشته باشد. main-migrate.py جایگزین نیز در آن پوشه وجود دارد.

«انتقال» Python 3

دستور Python 3 runtime که در بالای app.yaml به‌صورت نظر درآمده است تنها چیزی است که برای انتقال این برنامه به Python 3 لازم است. کد منبع ازقبل با Python 3 سازگار است، بنابراین نیازی به تغییر در آن نیست. برای استقرار این برنامه به‌عنوان برنامه Python 3، مراحل زیر را اجرا کنید:

  1. دستور Python 3 runtime را در بالای app.yaml از حالت نظر خارج کنید.
  2. همه خطوط دیگر را در app.yaml حذف کنید.
  3. فایل appengine_config.py را حذف کنید. (در زمان اجرای Python 3 استفاده نمی‌شود)
  4. پوشه lib را درصورت وجود حذف کنید. (با زمان اجرای Python 3 غیرضروری است)

مرتب‌سازی

عمومی

اگر کارتان تمام شده است، توصیه می‌کنیم برنامه App Engine خود را غیرفعال کنید تا از ایجاد صورت‌حساب جلوگیری شود. بااین‌حال، اگر می‌خواهید بیشتر آزمایش یا تجربه کنید، پلاتفرم App Engine دارای سهمیه رایگان است و تا زمانی که از آن سطح استفاده فراتر نروید، هزینه‌ای از شما کسر نخواهد شد. این هزینه برای محاسبه است، اما ممکن است برای سرویس‌های App Engine مربوطه نیز هزینه‌هایی وجود داشته باشد، بنابراین برای اطلاعات بیشتر صفحه قیمت‌گذاری آن را بررسی کنید. اگر این انتقال شامل سرویس‌های Cloud دیگر باشد، صورت‌حساب آن‌ها به‌طور جداگانه صادر می‌شود. در هر دو مورد، درصورت وجود، بخش «مختص این codelab» را در زیر ببینید.

برای شفاف‌سازی کامل، استقرار در پلتفرم محاسباتی بدون‌سرور Google Cloud مانند App Engine هزینه‌های جزئی ساخت و فضای ذخیره‌سازی را به‌همراه دارد. Cloud Build سهمیه رایگان خودش را دارد، همان‌طور که Cloud Storage دارد. ذخیره‌سازی آن تصویر بخشی از آن سهمیه را مصرف می‌کند. بااین‌حال، ممکن است در منطقه‌ای زندگی کنید که چنین سطح رایگانی ندارد، بنابراین از مصرف فضای ذخیره‌سازی خود آگاه باشید تا هزینه‌های احتمالی را به حداقل برسانید. «پوشه‌های» Cloud Storage خاصی که باید بررسی کنید عبارت‌اند از:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • پیوندهای فضای ذخیره‌سازی بالا به PROJECT_ID و *LOC*ation شما بستگی دارد، برای مثال، اگر برنامه‌تان در ایالات متحده میزبانی می‌شود، «us».

ازسوی دیگر، اگر نمی‌خواهید این برنامه یا سایر codelabهای انتقال مرتبط را ادامه دهید و می‌خواهید همه چیز را به‌طور کامل حذف کنید، پروژه‌تان را خاموش کنید.

مختص این codelab

سرویس‌های فهرست‌شده در زیر مختص این codelab است. برای کسب اطلاعات بیشتر، به اسناد هر محصول مراجعه کنید:

توجه داشته باشید که اگر از «واحد ۱۵» به «واحد ۱۶» مهاجرت کرده‌اید، همچنان در Blobstore داده خواهید داشت، به همین دلیل اطلاعات قیمت‌گذاری آن را در بالا آورده‌ایم.

مراحل بعدی

علاوه‌بر این آموزش، سایر ماژول‌های انتقال که بر دور شدن از سرویس‌های بسته‌ای قدیمی تمرکز دارند و باید درنظر گرفته شوند عبارت‌اند از:

App Engine دیگر تنها پلاتفرم بدون سرور در Google Cloud نیست. اگر برنامه App Engine کوچکی دارید یا برنامه‌ای دارید که عملکرد محدودی دارد و می‌خواهید آن را به ریزسرویس مستقل تبدیل کنید، یا می‌خواهید برنامه یکپارچه را به چند مؤلفه قابل‌استفاده مجدد تقسیم کنید، این‌ها دلایل خوبی برای درنظر گرفتن انتقال به Cloud Functions است. اگر کانتینرسازی بخشی از گردش کار توسعه برنامه شما شده است، به‌ویژه اگر شامل خط لوله CI/CD (ادغام مداوم/تحویل یا استقرار مداوم) باشد، انتقال به Cloud Run را درنظر بگیرید. این سناریوها در واحدهای زیر پوشش داده شده‌اند:

  • انتقال از App Engine به Cloud Functions: به واحد ۱۱ مراجعه کنید
  • انتقال از App Engine به Cloud Run: برای کانتینرسازی برنامه با Docker، به واحد ۴ مراجعه کنید، یا برای انجام این کار بدون کانتینر، دانش Docker، یا Dockerfile، به واحد ۵ مراجعه کنید

رفتن به پلاتفرم بدون سرور دیگر اختیاری است و توصیه می‌کنیم قبل‌از ایجاد هرگونه تغییر، بهترین گزینه‌ها را برای برنامه‌ها و موارد استفاده‌تان درنظر بگیرید.

صرف‌نظر از اینکه کدام ماژول انتقال را در مرحله بعد درنظر می‌گیرید، به همه محتوای Serverless Migration Station (codelabs، ویدیوها، کد منبع [درصورت دردسترس بودن]) می‌توان در مخزن متن‌باز آن دسترسی پیدا کرد. README مخزن همچنین راهنمایی‌هایی درباره اینکه کدام انتقال‌ها را باید درنظر گرفت و هرگونه «ترتیب» مربوط به «مدول‌های انتقال» ارائه می‌دهد.

‫۷. منابع بیشتر

مشکلات/ بازخورد Codelab

اگر مشکلی در این codelab پیدا کردید، لطفاً قبل‌از ثبت آن، ابتدا مشکلتان را جستجو کنید. پیوندهای جستجو و ایجاد مشکلات جدید:

منابع انتقال

پیوندهای پوشه‌های مخزن برای «واحد ۱۵» (شروع) و «واحد ۱۶» (پایان) را می‌توانید در جدول زیر پیدا کنید. همچنین می‌توانید ازطریق مخزن همه انتقال‌های کارگاه کد App Engine به آن‌ها دسترسی پیدا کنید و فایل ZIP را شبیه‌سازی یا بارگیری کنید.

Codelab

Python 2

Python 3

واحد ۱۵

کد

نامشخص

واحد ۱۶ (این codelab)

کد

(همانند Python 2)

منابع آنلاین

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

‫App Engine Blobstore و «فضای ذخیره‌سازی ابری»

پلاتفرم App Engine

اطلاعات دیگر Cloud

Python

ویدیوها

گواهینامه رانندگی

این اثر تحت «پروانه مشترکات خلاقانه با ذکر مؤلف نسخه ۲.۰» است.