Rumah > pembangunan bahagian belakang > Tutorial Python > Reka Bentuk Didorong Domain Asli dengan Flama

Reka Bentuk Didorong Domain Asli dengan Flama

Barbara Streisand
Lepaskan: 2024-11-03 14:08:03
asal
682 orang telah melayarinya

Anda mungkin pernah mendengar tentang keluaran terbaru Flama 1.7, yang membawa beberapa ciri baharu yang menarik untuk membantu anda dengan pembangunan dan pengeluaran API ML anda. Siaran ini dikhaskan dengan tepat kepada salah satu sorotan utama keluaran itu: Sokongan untuk Reka Bentuk Dipacu Domain. Tetapi, sebelum kami menyelami butiran dengan contoh langsung, kami mengesyorkan anda untuk mengingati sumber berikut (dan, biasakan diri dengannya jika anda belum melakukannya):

  • Dokumentasi rasmi Flama: Dokumentasi Flama
  • Pasca memperkenalkan Flama untuk API ML: Pengenalan kepada Flama untuk API Pembelajaran Mesin Teguh

Sekarang, mari kita mulakan dengan ciri baharu dan lihat cara anda boleh memanfaatkannya untuk membina API ML yang mantap dan boleh diselenggara.

Jadual kandungan

Siaran ini berstruktur seperti berikut:

  • Apakah Reka Bentuk Didorong Domain?
    • Ikhtisar Ringkas
    • Konsep Utama
  • Melaksanakan DDD dengan Flama
    • Menyediakan persekitaran pembangunan
    • Aplikasi asas
    • DDD sedang beraksi
  • Kesimpulan
  • Sokong kerja kami
  • Rujukan
  • Mengenai pengarang

Apakah Reka Bentuk Didorong Domain?

Gambaran Keseluruhan Ringkas

Dalam pembangunan perisian moden, menyelaraskan logik perniagaan dengan reka bentuk teknikal aplikasi adalah penting. Di sinilah Reka Bentuk Dipacu Domain (DDD) bersinar. DDD menekankan membina perisian yang mencerminkan domain teras perniagaan, memecahkan masalah kompleks dengan mengatur kod di sekitar konsep perniagaan. Dengan berbuat demikian, DDD membantu pembangun untuk mencipta aplikasi yang boleh diselenggara, berskala dan teguh. Dalam perkara berikut, kami memperkenalkan perkara yang kami anggap sebagai konsep DDD yang paling penting yang perlu anda ketahui. Sebelum kita menyelaminya, mari kita nyatakan bahawa siaran ini tidak bertujuan untuk menjadi panduan komprehensif kepada DDD, mahupun pengganti rujukan utama mengenai topik tersebut. Sesungguhnya, kami mengesyorkan sumber berikut untuk mendapatkan pemahaman yang lebih mendalam tentang DDD:

  • Cosmic Python oleh Harry Percival dan Bob Gregory: Buku ini merupakan sumber yang hebat untuk mempelajari cara menggunakan DDD dalam Python.
  • Reka Bentuk Dipacu Domain: Menangani Kerumitan di Hati Perisian oleh Eric Evans: Ini ialah buku yang memperkenalkan DDD kepada dunia, dan ia mesti dibaca oleh sesiapa sahaja yang berminat untuk membangunkan pemahaman mendalam tentang DDD.

Konsep Utama

Sebelum menyelam lebih mendalam kepada mana-mana konsep utama DDD, kami mengesyorkan anda melihat angka yang agak berguna oleh Cosmic Python di mana ini ditunjukkan dalam konteks apl, sekali gus menunjukkan cara ia saling berkaitan: angka .

Model Domain

Konsep model domain boleh dijelaskan dengan takrifan ringkas istilahnya:

  • domain merujuk kepada bidang subjek tertentu aktiviti (atau pengetahuan) yang perisian kami sedang dibina untuk menyokongnya.
  • model merujuk kepada perwakilan ringkas (atau pengabstrakan) sistem atau proses yang kami cuba kodkan dalam perisian kami.

Oleh itu, model domain ialah cara yang mewah (tetapi standard dan berguna) untuk merujuk kepada set konsep dan peraturan yang ada dalam fikiran pemilik perniagaan tentang cara perniagaan itu berfungsi. Inilah yang juga kami, dan lazimnya, rujuk sebagai logik perniagaan aplikasi, termasuk peraturan, kekangan dan perhubungan yang mengawal tingkah laku sistem.

Kami akan merujuk kepada model domain sebagai model mulai sekarang.

Corak repositori

Corak repositori ialah corak reka bentuk yang membolehkan penyahgandingan model daripada akses data. Idea utama di sebalik corak repositori adalah untuk mencipta lapisan abstraksi antara logik akses data dan logik perniagaan aplikasi. Lapisan abstraksi ini membolehkan pengasingan kebimbangan, menjadikan kod lebih boleh diselenggara dan boleh diuji.

Apabila melaksanakan corak repositori, kami biasanya mentakrifkan antara muka yang menentukan kaedah standard yang mesti dilaksanakan oleh mana-mana repositori lain (AbstractRepository). Dan, kemudian, repositori tertentu ditakrifkan dengan pelaksanaan konkrit kaedah ini di mana logik capaian data dilaksanakan (cth., SQLAlchemyRepository). Corak reka bentuk ini bertujuan untuk mengasingkan kaedah manipulasi data supaya ia boleh digunakan dengan lancar di tempat lain dalam aplikasi, mis. dalam model domain kami.

Unit corak kerja

Unit corak kerja ialah bahagian yang hilang untuk akhirnya memisahkan model daripada akses data. Unit kerja merangkum logik akses data dan menyediakan cara untuk mengumpulkan semua operasi yang mesti dilakukan pada sumber data dalam satu transaksi. Corak ini memastikan semua operasi dilakukan secara atom.

Apabila melaksanakan unit corak kerja, kami biasanya mentakrifkan antara muka yang menentukan kaedah standard yang mesti dilaksanakan oleh mana-mana unit kerja lain (AbstractUnitOfWork). Dan, kemudian, unit kerja tertentu ditakrifkan dengan pelaksanaan konkrit kaedah ini di mana logik akses data dilaksanakan (cth., SQLAlchemyUnitOfWork). Reka bentuk ini membolehkan pengendalian sistematik sambungan kepada sumber data, tanpa perlu mengubah pelaksanaan logik perniagaan aplikasi.

Melaksanakan DDD dengan Flama

Selepas pengenalan pantas kepada konsep utama DDD, kami bersedia untuk menyelami pelaksanaan DDD dengan Flama. Dalam bahagian ini, kami akan membimbing anda melalui proses menyediakan persekitaran pembangunan, membina aplikasi asas dan melaksanakan konsep DDD dengan Flama.

Sebelum meneruskan dengan contoh, sila lihat konvensyen penamaan Flama berkenaan konsep DDD utama yang baru kami semak:

Native Domain-Driven Design with Flama

Seperti yang anda lihat dalam rajah di atas, konvensyen penamaan agak intuitif: Repositori merujuk kepada corak repositori; dan, Pekerja merujuk kepada unit kerja. Kini, kita kini boleh beralih kepada pelaksanaan API Flama yang menggunakan DDD. Tetapi, sebelum kita mula, jika anda perlu menyemak asas tentang cara mencipta API mudah dengan flama, atau cara menjalankan API setelah kod anda sudah sedia, maka anda mungkin mahu menyemak keluar panduan permulaan pantas. Di sana, anda akan menemui konsep asas dan langkah yang diperlukan untuk mengikuti siaran ini. Sekarang, tanpa berlengah lagi, mari kita mulakan dengan pelaksanaannya.

Menyediakan persekitaran pembangunan

Langkah pertama kami ialah mencipta persekitaran pembangunan kami dan memasang semua kebergantungan yang diperlukan untuk projek ini. Perkara yang baik ialah untuk contoh ini kita hanya perlu memasang flama untuk mempunyai semua alatan yang diperlukan untuk melaksanakan pengesahan JWT. Kami akan menggunakan puisi untuk mengurus kebergantungan kami, tetapi anda juga boleh menggunakan pip jika anda lebih suka:

poetry add "flama[full]" "aiosqlite"
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Pakej aiosqlite diperlukan untuk menggunakan SQLite dengan SQLAlchemy, iaitu pangkalan data yang akan kami gunakan dalam contoh ini.

Jika anda ingin tahu cara kami biasanya mengatur projek kami, lihat siaran kami sebelum ini di sini, di mana kami menerangkan secara terperinci cara menyediakan projek ular sawa dengan puisi, dan struktur folder projek yang biasanya kami ikuti.

Aplikasi asas

Mari kita mulakan dengan aplikasi mudah yang mempunyai satu titik akhir awam. Titik akhir ini akan mengembalikan penerangan ringkas tentang API.

# src/app.py
from flama import Flama

app = Flama(
    title="Domain-driven API",
    version="1.0.0",
    description="Domain-driven design with Flama ?",
    docs="/docs/",
)

@app.get("/", name="info")
def info():
    """
    tags:
        - Info 
    summary:
        Ping
    description:
        Returns a brief description of the API
    responses:
        200:
            description:
                Successful ping.
    """
    return {"title": app.schema.title, "description": app.schema.description, "public": True}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Jika anda ingin menjalankan aplikasi ini, anda boleh menyimpan kod di atas dalam fail bernama app.py di bawah folder src, dan kemudian jalankan arahan berikut (ingat untuk mengaktifkan persekitaran puisi, jika tidak, anda perlu awalan perintah dengan larian puisi):

flama run --server-reload src.app:app

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

di mana bendera --server-reload adalah pilihan dan digunakan untuk memuat semula pelayan secara automatik apabila kod berubah. Ini sangat berguna semasa pembangunan, tetapi anda boleh mengeluarkannya jika anda tidak memerlukannya. Untuk senarai penuh pilihan yang tersedia, anda boleh menjalankan flama run --help, atau semak dokumentasi.

Sebagai alternatif, anda juga boleh menjalankan aplikasi dengan menjalankan skrip berikut, yang boleh anda simpan sebagai __main__.py di bawah folder src:

# src/__main__.py
import flama

def main():
    flama.run(
      flama_app="src.app:app", 
      server_host="0.0.0.0", 
      server_port=8000, 
      server_reload=True
    )

if __name__ == "__main__":
    main()
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dan kemudian, anda boleh menjalankan aplikasi dengan melaksanakan arahan berikut:

poetry add "flama[full]" "aiosqlite"
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

DDD sedang beraksi

Sekarang, setelah menyediakan rangka minimum untuk aplikasi kami, kami boleh mula melaksanakan konsep DDD yang baru kami semak dalam
konteks contoh mudah yang cuba meniru senario dunia sebenar. Katakan kami diminta untuk membangunkan API untuk mengurus pengguna, dan kami dibekalkan dengan keperluan berikut:

  • Kami mahu mencipta pengguna baharu melalui permintaan POST kepada /pengguna/, memberikan nama pengguna, nama keluarga, e-mel dan kata laluan.
  • Mana-mana pengguna yang dibuat akan disimpan dalam pangkalan data dengan skema berikut:
    • id: pengecam unik untuk pengguna.
    • nama: nama pengguna.
    • nama keluarga: nama keluarga pengguna.
    • e-mel: e-mel pengguna.
    • kata laluan: kata laluan pengguna. Ini harus dicincang sebelum menyimpannya dalam pangkalan data.
    • aktif: bendera boolean untuk menunjukkan sama ada pengguna aktif atau tidak. Secara lalai, pengguna dicipta sebagai tidak aktif.
  • Pengguna yang dibuat mesti mengaktifkan akaun mereka dengan menghantar permintaan POST ke /pengguna/aktifkan/ dengan e-mel dan kata laluan mereka. Setelah pengguna diaktifkan, status pengguna mesti dikemas kini dalam pangkalan data untuk aktif.
  • Pengguna boleh log masuk dengan menghantar permintaan POST kepada /pengguna/log masuk/ dengan e-mel dan kata laluan mereka. Jika pengguna aktif, API mesti mengembalikan semua maklumat pengguna. Jika tidak, API mesti mengembalikan mesej ralat.
  • Pengguna yang ingin menyahaktifkan akaun mereka boleh berbuat demikian dengan menghantar permintaan POST ke /pengguna/nyahaktifkan/ dengan e-mel dan kata laluan mereka. Setelah pengguna dinyahaktifkan, status pengguna mesti dikemas kini dalam pangkalan data kepada tidak aktif.

Set keperluan ini membentuk perkara yang sebelum ini kami rujuk sebagai model domain aplikasi kami, yang pada asasnya tidak lain hanyalah pewujudan aliran kerja pengguna berikut:

  1. Seorang pengguna dibuat melalui permintaan POST kepada /user/.
  2. Pengguna mengaktifkan akaun mereka melalui permintaan POST ke /user/activate/.
  3. Pengguna log masuk melalui permintaan POST ke /user/login/.
  4. Pengguna menyahaktifkan akaun mereka melalui permintaan POST ke /user/deactivate/.
  5. Pengguna boleh mengulangi langkah 2-4 seberapa banyak yang mereka mahu.

Sekarang, mari kita laksanakan model domain menggunakan repositori dan corak pekerja. Kami akan mulakan dengan mentakrifkan model data, dan kemudian kami akan melaksanakan repositori dan corak pekerja.

Model data

Data pengguna kami akan disimpan dalam pangkalan data SQLite (anda boleh menggunakan mana-mana pangkalan data lain yang disokong oleh SQLAlchemy). Kami akan menggunakan model data berikut untuk mewakili pengguna (anda boleh menyimpan kod ini dalam fail yang dipanggil models.py di bawah folder src):

# src/app.py
from flama import Flama

app = Flama(
    title="Domain-driven API",
    version="1.0.0",
    description="Domain-driven design with Flama ?",
    docs="/docs/",
)

@app.get("/", name="info")
def info():
    """
    tags:
        - Info 
    summary:
        Ping
    description:
        Returns a brief description of the API
    responses:
        200:
            description:
                Successful ping.
    """
    return {"title": app.schema.title, "description": app.schema.description, "public": True}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Selain model data, kami memerlukan skrip migrasi untuk mencipta pangkalan data dan jadual. Untuk ini, kami boleh menyimpan kod berikut dalam fail yang dipanggil migrations.py pada akar projek:

poetry add "flama[full]" "aiosqlite"
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dan kemudian, kita boleh menjalankan skrip migrasi dengan melaksanakan arahan berikut:

# src/app.py
from flama import Flama

app = Flama(
    title="Domain-driven API",
    version="1.0.0",
    description="Domain-driven design with Flama ?",
    docs="/docs/",
)

@app.get("/", name="info")
def info():
    """
    tags:
        - Info 
    summary:
        Ping
    description:
        Returns a brief description of the API
    responses:
        200:
            description:
                Successful ping.
    """
    return {"title": app.schema.title, "description": app.schema.description, "public": True}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Repositori

Dalam contoh ini kita hanya memerlukan satu repositori, iaitu repositori yang akan mengendalikan operasi atom pada jadual pengguna, yang namanya akan menjadi UserRepository. Syukurlah, flama menyediakan kelas asas untuk repositori yang berkaitan dengan jadual SQLAlchemy, dipanggil SQLAlchemyTableRepository.

Kelas SQLAlchemyTableRepository menyediakan satu set kaedah untuk melaksanakan operasi CRUD pada jadual, khususnya:

  • create: Mencipta elemen baharu dalam jadual. Jika elemen sudah wujud, ia akan menimbulkan pengecualian (IntegrityError), jika tidak, ia akan mengembalikan kunci utama elemen baharu.
  • retrieve: Mengambil elemen daripada jadual. Jika elemen tidak wujud, ia akan menimbulkan pengecualian (NotFoundError), jika tidak ia akan mengembalikan elemen tersebut. Jika lebih daripada satu elemen ditemui, ia akan menimbulkan pengecualian (MultipleRecordsError).
  • kemas kini: Mengemas kini elemen dalam jadual. Jika elemen tidak wujud, ia akan menimbulkan pengecualian (NotFoundError), jika tidak ia akan mengembalikan elemen yang dikemas kini.
  • delete: Memadam elemen daripada jadual.
  • senarai: Menyenaraikan semua elemen dalam jadual yang sepadan dengan klausa dan penapis yang diluluskan. Jika tiada klausa atau penapis diberikan, ia mengembalikan semua elemen dalam jadual. Jika tiada unsur ditemui, ia mengembalikan senarai kosong.
  • drop: Menggugurkan jadual daripada pangkalan data.

Untuk tujuan contoh kami, kami tidak memerlukan sebarang tindakan lanjut pada jadual, jadi kaedah yang disediakan oleh SQLAlchemyTableRepository adalah mencukupi. Kami boleh menyimpan kod berikut dalam fail yang dipanggil repositories.py di bawah folder src:

flama run --server-reload src.app:app

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Seperti yang anda boleh lihat, kelas UserRepository ialah subkelas SQLAlchemyTableRepository, dan ia hanya memerlukan jadual untuk ditetapkan dalam atribut _table. Ini adalah satu-satunya perkara yang perlu kita lakukan untuk mempunyai repositori berfungsi sepenuhnya untuk jadual pengguna.

Jika kami ingin menambah kaedah tersuai di luar operasi CRUD standard, kami boleh melakukannya dengan mentakrifkannya dalam kelas UserRepository. Contohnya, jika kami ingin menambah kaedah untuk mengira bilangan pengguna aktif, kami boleh berbuat demikian seperti berikut:

# src/__main__.py
import flama

def main():
    flama.run(
      flama_app="src.app:app", 
      server_host="0.0.0.0", 
      server_port=8000, 
      server_reload=True
    )

if __name__ == "__main__":
    main()
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Walaupun kami tidak akan menggunakan kaedah ini dalam contoh kami, adalah baik untuk mengetahui bahawa kami boleh menambah kaedah tersuai pada repositori jika perlu, dan cara ia dilaksanakan
dalam konteks corak repositori. Ini adalah corak reka bentuk yang berkuasa seperti yang kita sudah lihat, kerana kita boleh melaksanakan di sini semua logik akses data tanpa perlu menukar logik perniagaan aplikasi (yang dilaksanakan dalam kaedah sumber yang sepadan).

Pekerja

Corak unit kerja digunakan untuk merangkum logik akses data dan menyediakan cara untuk mengumpulkan semua operasi yang mesti dilakukan pada sumber data dalam satu transaksi. Dalam flama corak UoW dilaksanakan dengan nama Pekerja. Dengan cara yang sama seperti corak repositori, flama menyediakan kelas asas untuk pekerja yang berkaitan dengan jadual SQLAlchemy, dipanggil SQLAlchemyWorker. Pada dasarnya, SQLAlchemyWorker menyediakan sambungan dan transaksi kepada pangkalan data, dan membuat instantiate semua repositorinya dengan sambungan pekerja. Dalam contoh ini, pekerja kami hanya akan menggunakan satu repositori (iaitu, UserRepository) tetapi kami boleh menambah lebih banyak repositori jika perlu.

Pekerja kami akan dipanggil RegisterWorker, dan kami boleh menyimpan kod berikut dalam fail bernama workers.py di bawah folder src:

poetry add "flama[full]" "aiosqlite"
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Oleh itu, jika kami mempunyai lebih banyak repositori untuk digunakan, contohnya ProductRepository dan OrderRepository, kami boleh menambahkannya kepada pekerja seperti berikut:

# src/app.py
from flama import Flama

app = Flama(
    title="Domain-driven API",
    version="1.0.0",
    description="Domain-driven design with Flama ?",
    docs="/docs/",
)

@app.get("/", name="info")
def info():
    """
    tags:
        - Info 
    summary:
        Ping
    description:
        Returns a brief description of the API
    responses:
        200:
            description:
                Successful ping.
    """
    return {"title": app.schema.title, "description": app.schema.description, "public": True}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Semudah itu, kami telah melaksanakan repositori dan corak pekerja dalam aplikasi kami. Kini, kita boleh meneruskan untuk melaksanakan kaedah sumber yang akan menyediakan titik akhir API yang diperlukan untuk berinteraksi dengan data pengguna.

Sumber

Sumber ialah salah satu blok binaan utama aplikasi flama. Ia digunakan untuk mewakili sumber aplikasi (dalam erti kata sumber RESTful) dan untuk menentukan titik akhir API yang berinteraksi dengannya.

Dalam contoh kami, kami akan menentukan sumber untuk pengguna, dipanggil UserResource, yang akan mengandungi kaedah untuk mencipta, mengaktifkan, log masuk dan menyahaktifkan pengguna. Sumber perlu diperoleh, sekurang-kurangnya, daripada flama kelas Sumber terbina dalam, walaupun flama menyediakan kelas yang lebih canggih untuk digunakan seperti RESTResourcedan CRUDResource.

Kami boleh menyimpan kod berikut dalam fail yang dipanggil resources.py di bawah folder src:

flama run --server-reload src.app:app

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Aplikasi asas dengan DDD

Sekarang kami telah melaksanakan model data, repositori dan corak pekerja serta kaedah sumber, kami perlu mengubah suai aplikasi asas yang kami perkenalkan sebelum ini, supaya semuanya berfungsi seperti yang diharapkan. Kita perlu:

  • Tambah sambungan SQLAlchemy pada aplikasi dan ini dicapai dengan menambahkan SQLAlchemyModule pada pembina aplikasi sebagai modul.
  • Tambahkan pekerja pada aplikasi dan ini dicapai dengan menambahkan RegisterWorker pada pembina aplikasi sebagai komponen.

Ini akan meninggalkan fail app.py seperti berikut:

poetry add "flama[full]" "aiosqlite"
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Sepatutnya anda sudah jelas bagaimana corak DDD telah membolehkan kami memisahkan logik perniagaan aplikasi (yang mudah dibaca dalam kaedah sumber) daripada logik akses data (yang dilaksanakan dalam repositori dan corak pekerja). Perlu juga diperhatikan bagaimana pemisahan kebimbangan ini telah menjadikan kod lebih boleh diselenggara dan boleh diuji serta cara kod itu kini lebih sejajar dengan keperluan perniagaan yang kami berikan pada permulaan contoh ini.

Menjalankan aplikasi

Sebelum menjalankan sebarang arahan, sila semak sama ada persekitaran pembangunan anda disediakan dengan betul dan struktur folder adalah seperti berikut:

# src/app.py
from flama import Flama

app = Flama(
    title="Domain-driven API",
    version="1.0.0",
    description="Domain-driven design with Flama ?",
    docs="/docs/",
)

@app.get("/", name="info")
def info():
    """
    tags:
        - Info 
    summary:
        Ping
    description:
        Returns a brief description of the API
    responses:
        200:
            description:
                Successful ping.
    """
    return {"title": app.schema.title, "description": app.schema.description, "public": True}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Jika semuanya disediakan dengan betul, anda boleh menjalankan aplikasi dengan melaksanakan arahan berikut (ingat untuk menjalankan skrip pemindahan sebelum menjalankan aplikasi):

flama run --server-reload src.app:app

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kini kita boleh mencuba logik perniagaan yang baru kita laksanakan. Ingat, anda boleh mencuba ini sama ada dengan menggunakan alat seperti curl atau Postman, atau dengan menggunakan UI dokumen yang dijana secara automatik yang disediakan oleh flama dengan menavigasi ke http://localhost:8000/docs/ dalam penyemak imbas anda dan mencuba titik akhir dari sana.

Native Domain-Driven Design with Flama

Buat pengguna

Untuk mencipta pengguna, anda boleh menghantar permintaan POST kepada /user/ dengan muatan berikut:

# src/__main__.py
import flama

def main():
    flama.run(
      flama_app="src.app:app", 
      server_host="0.0.0.0", 
      server_port=8000, 
      server_reload=True
    )

if __name__ == "__main__":
    main()
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Jadi, kita boleh menggunakan curl untuk menghantar permintaan seperti berikut:

poetry run python src/__main__.py

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk

Jika permintaan itu berjaya, anda akan menerima 200 respons dengan badan kosong, dan pengguna akan dibuat dalam pangkalan data.

Log masuk

Untuk log masuk, anda boleh menghantar permintaan POST kepada /user/login/ dengan muatan berikut:

# src/models.py
import uuid

import sqlalchemy
from flama.sqlalchemy import metadata
from sqlalchemy.dialects.postgresql import UUID

__all__ = ["user_table", "metadata"]

user_table = sqlalchemy.Table(
    "user",
    metadata,
    sqlalchemy.Column("id", UUID(as_uuid=True), primary_key=True, nullable=False, default=uuid.uuid4),
    sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
    sqlalchemy.Column("surname", sqlalchemy.String, nullable=False),
    sqlalchemy.Column("email", sqlalchemy.String, nullable=False, unique=True),
    sqlalchemy.Column("password", sqlalchemy.String, nullable=False),
    sqlalchemy.Column("active", sqlalchemy.Boolean, nullable=False),
)
Salin selepas log masuk
Salin selepas log masuk

Jadi, kita boleh menggunakan curl untuk menghantar permintaan seperti berikut:

# migrations.py
from sqlalchemy import create_engine

from src.models import metadata

if __name__ == "__main__":
    # Set up the SQLite database
    engine = create_engine("sqlite:///models.db", echo=False)

    # Create the database tables
    metadata.create_all(engine)

    # Print a success message
    print("Database and User table created successfully.")
Salin selepas log masuk
Salin selepas log masuk

Memandangkan pengguna tidak aktif, anda akan menerima sesuatu seperti respons berikut:

> poetry run python migrations.py

Database and User table created successfully.
Salin selepas log masuk

Kami juga boleh menguji perkara yang akan berlaku jika seseorang cuba log masuk dengan kata laluan yang salah:

# src/repositories.py
from flama.ddd import SQLAlchemyTableRepository

from src import models

__all__ = ["UserRepository"]

class UserRepository(SQLAlchemyTableRepository):
    _table = models.user_table
Salin selepas log masuk

Dalam kes ini, anda sepatutnya menerima respons 401 dengan badan berikut:

# src/repositories.py
from flama.ddd import SQLAlchemyTableRepository

from src import models

__all__ = ["UserRepository"]

class UserRepository(SQLAlchemyTableRepository):
    _table = models.user_table

    async def count_active_users(self):
        return len((await self._connection.execute(self._table.select().where(self._table.c.active == True))).all())
Salin selepas log masuk

Akhir sekali, kita juga harus cuba log masuk dengan pengguna yang tidak wujud:

# src/workers.py
from flama.ddd import SQLAlchemyWorker

from src import repositories

__all__ = ["RegisterWorker"]


class RegisterWorker(SQLAlchemyWorker):
    user: repositories.UserRepository
Salin selepas log masuk

Dalam kes ini, anda sepatutnya menerima respons 404 dengan badan berikut:

# src/workers.py
from flama.ddd import SQLAlchemyWorker

from src import repositories

__all__ = ["RegisterWorker"]

class RegisterWorker(SQLAlchemyWorker):
    user: repositories.UserRepository
    product: repositories.ProductRepository
    order: repositories.OrderRepository
Salin selepas log masuk
Pengaktifan pengguna

Setelah meneroka proses log masuk, kami kini boleh mengaktifkan pengguna dengan menghantar permintaan POST ke /pengguna/aktifkan/ dengan bukti kelayakan pengguna:

# src/resources.py
import hashlib
import http
import uuid

from flama import types
from flama.ddd.exceptions import NotFoundError
from flama.exceptions import HTTPException
from flama.http import APIResponse
from flama.resources import Resource, resource_method

from src import models, schemas, worker

__all__ = ["AdminResource", "UserResource"]

ENCRYPTION_SALT = uuid.uuid4().hex
ENCRYPTION_PEPER = uuid.uuid4().hex

class Password:
    def __init__(self, password: str):
        self._password = password

    def encrypt(self):
        return hashlib.sha512(
            (hashlib.sha512((self._password + ENCRYPTION_SALT).encode()).hexdigest() + ENCRYPTION_PEPER).encode()
        ).hexdigest()

class UserResource(Resource):
    name = "user"
    verbose_name = "User"

    @resource_method("/", methods=["POST"], name="create")
    async def create(self, worker: worker.RegisterWorker, data: types.Schema[schemas.UserDetails]):
        """
        tags:
            - User
        summary:
            User create
        description:
            Create a user
        responses:
            200:
                description:
                    User created in successfully.
        """
        async with worker:
            try:
                await worker.user.retrieve(email=data["email"])
            except NotFoundError:
                await worker.user.create({**data, "password": Password(data["password"]).encrypt(), "active": False})

        return APIResponse(status_code=http.HTTPStatus.OK)

    @resource_method("/signin/", methods=["POST"], name="signin")
    async def signin(self, worker: worker.RegisterWorker, data: types.Schema[schemas.UserCredentials]):
        """
        tags:
            - User
        summary:
            User sign in
        description:
            Create a user
        responses:
            200:
                description:
                    User signed in successfully.
            401:
                description:
                    User not active.
            404:
                description:
                    User not found.
        """
        async with worker:
            password = Password(data["password"])
            try:
                user = await worker.user.retrieve(email=data["email"])
            except NotFoundError:
                raise HTTPException(status_code=http.HTTPStatus.NOT_FOUND)

            if user["password"] != password.encrypt():
                raise HTTPException(status_code=http.HTTPStatus.UNAUTHORIZED)

            if not user["active"]:
                raise HTTPException(
                    status_code=http.HTTPStatus.BAD_REQUEST, detail=f"User must be activated via /user/activate/"
                )

        return APIResponse(status_code=http.HTTPStatus.OK, schema=types.Schema[schemas.User], content=user)

    @resource_method("/activate/", methods=["POST"], name="activate")
    async def activate(self, worker: worker.RegisterWorker, data: types.Schema[schemas.UserCredentials]):
        """
        tags:
            - User
        summary:
            User activate
        description:
            Activate an existing user
        responses:
            200:
                description:
                    User activated successfully.
            401:
                description:
                    User activation failed due to invalid credentials.
            404:
                description:
                    User not found.
        """
        async with worker:
            try:
                user = await worker.user.retrieve(email=data["email"])
            except NotFoundError:
                raise HTTPException(status_code=http.HTTPStatus.NOT_FOUND)

            if user["password"] != Password(data["password"]).encrypt():
                raise HTTPException(status_code=http.HTTPStatus.UNAUTHORIZED)

            if not user["active"]:
                await worker.user.update({**user, "active": True}, id=user["id"])

        return APIResponse(status_code=http.HTTPStatus.OK)

    @resource_method("/deactivate/", methods=["POST"], name="deactivate")
    async def deactivate(self, worker: worker.RegisterWorker, data: types.Schema[schemas.UserCredentials]):
        """
        tags:
            - User
        summary:
            User deactivate
        description:
            Deactivate an existing user
        responses:
            200:
                description:
                    User deactivated successfully.
            401:
                description:
                    User deactivation failed due to invalid credentials.
            404:
                description:
                    User not found.
        """
        async with worker:
            try:
                user = await worker.user.retrieve(email=data["email"])
            except NotFoundError:
                raise HTTPException(status_code=http.HTTPStatus.NOT_FOUND)

            if user["password"] != Password(data["password"]).encrypt():
                raise HTTPException(status_code=http.HTTPStatus.UNAUTHORIZED)

            if user["active"]:
                await worker.user.update({**user, "active": False}, id=user["id"])

        return APIResponse(status_code=http.HTTPStatus.OK)
Salin selepas log masuk

Dengan permintaan ini, pengguna harus diaktifkan, dan anda akan menerima 200 respons dengan badan kosong.

Seperti dalam kes sebelumnya, kami juga boleh menguji apa yang akan berlaku jika seseorang cuba mengaktifkan pengguna dengan kata laluan yang salah:

poetry add "flama[full]" "aiosqlite"
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dalam kes ini, anda sepatutnya menerima respons 401 dengan badan berikut:

# src/app.py
from flama import Flama

app = Flama(
    title="Domain-driven API",
    version="1.0.0",
    description="Domain-driven design with Flama ?",
    docs="/docs/",
)

@app.get("/", name="info")
def info():
    """
    tags:
        - Info 
    summary:
        Ping
    description:
        Returns a brief description of the API
    responses:
        200:
            description:
                Successful ping.
    """
    return {"title": app.schema.title, "description": app.schema.description, "public": True}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Akhir sekali, kita juga harus cuba mengaktifkan pengguna yang tidak wujud:

flama run --server-reload src.app:app

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dalam kes ini, anda sepatutnya menerima respons 404 dengan badan berikut:

# src/__main__.py
import flama

def main():
    flama.run(
      flama_app="src.app:app", 
      server_host="0.0.0.0", 
      server_port=8000, 
      server_reload=True
    )

if __name__ == "__main__":
    main()
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Log masuk pengguna selepas pengaktifan

Sekarang pengguna diaktifkan, kami boleh cuba log masuk semula:

poetry run python src/__main__.py

INFO:     Started server process [3267]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Salin selepas log masuk
Salin selepas log masuk

Yang, kali ini, harus mengembalikan 200 respons dengan maklumat pengguna:

# src/models.py
import uuid

import sqlalchemy
from flama.sqlalchemy import metadata
from sqlalchemy.dialects.postgresql import UUID

__all__ = ["user_table", "metadata"]

user_table = sqlalchemy.Table(
    "user",
    metadata,
    sqlalchemy.Column("id", UUID(as_uuid=True), primary_key=True, nullable=False, default=uuid.uuid4),
    sqlalchemy.Column("name", sqlalchemy.String, nullable=False),
    sqlalchemy.Column("surname", sqlalchemy.String, nullable=False),
    sqlalchemy.Column("email", sqlalchemy.String, nullable=False, unique=True),
    sqlalchemy.Column("password", sqlalchemy.String, nullable=False),
    sqlalchemy.Column("active", sqlalchemy.Boolean, nullable=False),
)
Salin selepas log masuk
Salin selepas log masuk
Penyahaktifan pengguna

Akhir sekali, kami boleh menyahaktifkan pengguna dengan menghantar permintaan POST ke /pengguna/nyahaktifkan/ dengan kelayakan pengguna:

# migrations.py
from sqlalchemy import create_engine

from src.models import metadata

if __name__ == "__main__":
    # Set up the SQLite database
    engine = create_engine("sqlite:///models.db", echo=False)

    # Create the database tables
    metadata.create_all(engine)

    # Print a success message
    print("Database and User table created successfully.")
Salin selepas log masuk
Salin selepas log masuk

Dengan permintaan ini, pengguna harus dinyahaktifkan, dan anda akan menerima 200 respons dengan badan kosong.

Kesimpulan

Dalam siaran ini kami telah menceburi dunia Reka Bentuk Dipacu Domain (DDD) dan cara ia boleh dilaksanakan dalam aplikasi flama. Kami telah melihat cara DDD boleh membantu kami untuk memisahkan logik perniagaan aplikasi daripada logik akses data, dan cara pengasingan kebimbangan ini boleh menjadikan kod lebih boleh diselenggara dan boleh diuji. Kami juga telah melihat cara repositori dan corak pekerja boleh dilaksanakan dalam aplikasi flama dan cara ia boleh digunakan untuk merangkum logik akses data dan menyediakan cara untuk mengumpulkan semua operasi yang mesti dilakukan pada sumber data dalam satu transaksi. Akhir sekali, kami telah melihat cara kaedah sumber boleh digunakan untuk menentukan titik akhir API yang berinteraksi dengan data pengguna dan cara corak DDD boleh digunakan untuk melaksanakan keperluan perniagaan yang kami berikan pada permulaan contoh ini.

Walaupun proses log masuk yang telah kami huraikan di sini tidak sepenuhnya realistik, anda boleh menggabungkan bahan ini dan siaran sebelumnya tentang pengesahan JWT untuk melaksanakan proses yang lebih realistik, di mana log masuk itu akhirnya mengembalikan Token JWT. Jika anda berminat dengan ini, anda boleh menyemak siaran pada pengesahan JWT dengan flama.

Kami harap anda mendapati siaran ini berguna dan anda kini bersedia untuk melaksanakan DDD dalam aplikasi flama anda sendiri. Jika anda mempunyai sebarang soalan atau komen, sila hubungi kami. Kami sentiasa berbesar hati untuk membantu!

Nantikan lebih banyak siaran tentang flama dan topik menarik lain dalam dunia AI dan pembangunan perisian. Sehingga kali seterusnya!

Sokong kerja kami

Jika anda menyukai apa yang kami lakukan, terdapat cara percuma dan mudah untuk menyokong kerja kami. Hadiahkan kami ⭐ di Flama.

GitHub ⭐ bermakna dunia kepada kami dan memberi kami bahan api yang paling manis untuk terus mengusahakannya bagi membantu orang lain dalam perjalanannya membina API Pembelajaran Mesin yang mantap.

Anda juga boleh mengikuti kami di ?, tempat kami berkongsi berita dan kemas kini terkini kami, selain urutan menarik tentang AI, pembangunan perisian dan banyak lagi.

Rujukan

  • Dokumentasi Flama
  • Repositori Flama GitHub
  • Pakej Flama PyPI

Mengenai pengarang

  • Vortico: Kami pakar dalam pembangunan perisian untuk membantu perniagaan meningkatkan dan mengembangkan keupayaan AI dan teknologi mereka.

Atas ialah kandungan terperinci Reka Bentuk Didorong Domain Asli dengan Flama. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan