Bagaimana kami membangun UI untuk sistem iklan

gambar

Alih-alih bergabung


Sebelumnya di blog kami, kami menulis apa yang dilakukan IPONWEB - kami mengotomatiskan tampilan iklan di Internet. Sistem kami membuat keputusan tidak hanya berdasarkan data historis, tetapi juga secara aktif menggunakan informasi yang diperoleh secara real time. Dalam kasus DSP (Platform Sisi Permintaan - platform iklan untuk pengiklan), pengiklan (atau perwakilannya) harus membuat dan mengunggah spanduk iklan (kreatif) dalam salah satu format (gambar, video, spanduk interaktif, gambar + teks, dll.) , pilih audiens pengguna yang akan ditampilkan spanduk ini, tentukan berapa kali dimungkinkan untuk menampilkan iklan kepada satu pengguna, di negara mana, di situs mana, di perangkat mana, dan mencerminkan ini (dan lebih banyak lagi) dalam pengaturan penargetan kampanye iklan, serta mendistribusikan iklan anggaran s. Untuk SSP (Supply Side Platform - platform iklan untuk pemilik platform iklan), pemilik situs (aplikasi seluler, papan iklan, saluran televisi) harus menentukan tempat iklan di situsnya dan menunjukkan, misalnya, kategori iklan mana yang siap ditampilkannya. Semua pengaturan ini dibuat secara manual sebelumnya (bukan pada saat menampilkan iklan) menggunakan antarmuka pengguna. Dalam artikel ini saya akan berbicara tentang pendekatan kami untuk membangun antarmuka seperti itu, asalkan ada banyak, mereka mirip satu sama lain dan pada saat yang sama memiliki karakteristik masing-masing.

Bagaimana semuanya dimulai


gambar

Kami memulai bisnis periklanan pada tahun 2007, tetapi kami tidak segera melakukan antarmuka, tetapi hanya pada tahun 2014. Kami secara tradisional terlibat dalam pengembangan platform khusus yang sepenuhnya dirancang sesuai dengan spesifik bisnis masing-masing klien - di antara puluhan platform yang kami bangun, tidak ada dua yang identik. Dan karena platform periklanan kami dirancang tanpa batasan pada kemungkinan penyesuaian, antarmuka pengguna harus memenuhi persyaratan yang sama.

Ketika kami menerima permintaan pertama untuk antarmuka iklan untuk DSP lima tahun yang lalu, pilihan kami jatuh pada tumpukan teknologi yang populer dan nyaman: JavaScript dan AngularJS di ujung depan, dan backend pada Python, Django dan Django Rest Framework (DRF). Dari ini, proyek yang paling biasa dibuat, tugas utamanya adalah untuk menyediakan fungsionalitas CRUD. Hasil karyanya adalah file pengaturan untuk sistem periklanan dalam format XML. Sekarang, protokol interaksi seperti itu mungkin tampak aneh, tetapi, seperti yang telah kita bahas, sistem periklanan pertama (bahkan tanpa UI) kami mulai membangun yang "nol", dan format ini telah dipertahankan hingga hari ini.

Setelah peluncuran proyek pertama yang berhasil, berikut ini tidak butuh waktu lama. Ini juga UI untuk DSP dan persyaratan untuknya sama dengan untuk proyek pertama. Hampir. Terlepas dari kenyataan bahwa semuanya sangat mirip, iblis bersembunyi di detail - ada hierarki objek yang sedikit berbeda, beberapa bidang ditambahkan di sana ... Cara yang paling jelas untuk mendapatkan proyek kedua, sangat mirip dengan yang pertama, tetapi dengan perbaikan, adalah metode replikasi, yang kami gunakan . Dan itu memerlukan masalah yang umum bagi banyak orang - bersama dengan kode "baik", bug juga disalin, tambalan yang harus didistribusikan dengan tangan. Hal yang sama terjadi dengan semua fitur baru yang diluncurkan pada semua proyek aktif.

Dalam mode ini, adalah mungkin untuk bekerja ketika ada beberapa proyek, tetapi ketika jumlahnya melebihi 20, pendekatan yang sudah dikenalnya tidak lagi berskala. Oleh karena itu, kami memutuskan untuk mentransfer bagian umum dari proyek ke perpustakaan, dari mana proyek akan menghubungkan komponen yang dibutuhkan. Jika bug terdeteksi, ia diperbaiki sekali di perpustakaan dan secara otomatis didistribusikan ke proyek ketika versi perpustakaan diperbarui, dan hal yang sama terjadi dengan penggunaan kembali fitur baru.

Konfigurasi dan terminologi


Kami memiliki beberapa iterasi dalam implementasi pendekatan ini, dan semuanya mengalir satu sama lain secara evolusioner, dimulai dengan proyek kami yang biasa tentang DRF murni. Dalam implementasi terbaru, proyek kami dijelaskan menggunakan DSL berbasis JSON (lihat gambar). JSON ini menjelaskan struktur komponen proyek dan interkoneksi mereka, dan frontend dan backend dapat membacanya.

Setelah menginisialisasi aplikasi Angular, frontend meminta konfigurasi JSON dari backend. Backend tidak hanya memberikan file konfigurasi statis, tetapi juga memprosesnya, menambahkan berbagai metadata atau menghapus bagian dari konfigurasi yang bertanggung jawab atas bagian-bagian sistem yang tidak dapat diakses oleh pengguna. Ini memungkinkan Anda menunjukkan kepada pengguna yang berbeda antarmuka dengan cara yang berbeda, termasuk formulir interaktif, gaya CSS seluruh aplikasi dan elemen desain spesifik. Yang terakhir ini terutama berlaku untuk antarmuka pengguna platform yang digunakan oleh berbagai jenis klien dengan peran dan tingkat akses yang berbeda.

gambar

Backend, tidak seperti frontend, membaca konfigurasi sekali pada tahap inisialisasi aplikasi Django. Dengan demikian, jumlah fungsionalitas penuh dicatat pada backend, dan akses ke berbagai bagian sistem diperiksa dengan cepat.

Sebelum pindah ke bagian yang paling menarik - struktur basis data - Saya ingin memperkenalkan beberapa konsep yang kami gunakan ketika kami berbicara tentang struktur proyek kami agar berada pada gelombang yang sama dengan pembaca.

Konsep-konsep ini - Entitas dan Fitur - diilustrasikan dengan baik pada formulir entri data (lihat gambar). Seluruh bentuk adalah Entitas, dan bidang individual di dalamnya adalah Fitur. Gambar juga menunjukkan Endpoint (untuk berjaga-jaga). Jadi, Entity adalah objek independen dalam sistem di mana operasi CRUD dapat dilakukan, sementara Fitur hanya bagian dari "sesuatu yang lebih", bagian dari Entity. Dengan Fitur, Anda tidak dapat melakukan operasi CRUD tanpa terikat ke Entitas apa pun. Misalnya: anggaran kampanye iklan tanpa referensi kampanye itu sendiri hanyalah angka yang tidak dapat digunakan tanpa informasi tentang kampanye induk.

gambar

Konsep yang sama dapat ditemukan dalam konfigurasi JSON proyek (lihat gambar).

gambar

Struktur basis data


Bagian paling menarik dari proyek kami adalah struktur basis data dan mekanisme yang mendukungnya. Setelah mulai menggunakan PostgreSQL untuk versi pertama proyek kami, kami tetap menggunakan teknologi ini hari ini. Seiring dengan ini, kami secara aktif menggunakan Django ORM. Dalam implementasi awal, kami menggunakan model standar hubungan antara objek (entitas) pada Kunci Asing, namun, pendekatan ini menyebabkan kesulitan ketika perlu untuk mengubah hierarki hubungan. Jadi, misalnya, dalam hierarki standar Unit Bisnis DSP -> Pengiklan -> Kampanye, beberapa pelanggan harus masuk ke tingkat Agensi (Unit Bisnis -> Agensi -> Pengiklan -> ...). Oleh karena itu, kami secara bertahap meninggalkan penggunaan Kunci Asing dan mengatur tautan antara objek menggunakan tautan Banyak Ke Banyak melalui tabel terpisah, kami menyebutnya `LinkRegistry`.

Selain itu, kami secara bertahap meninggalkan hardcode untuk mengisi entitas dan mulai menyimpan sebagian besar bidang dalam tabel terpisah, menautkannya juga melalui `LinkRegistry` (lihat gambar). Mengapa ini dibutuhkan? Untuk setiap klien, konten entitas dapat bervariasi - beberapa bidang akan ditambahkan atau dihapus. Ternyata kami harus menyimpan superset bidang di setiap entitas untuk semua pelanggan kami. Pada saat yang sama, mereka semua harus dibuat opsional, sehingga bidang wajib โ€œasingโ€ tidak mengganggu pekerjaan.

gambar

Perhatikan contoh dalam gambar: di sini struktur basis data untuk materi iklan dengan satu bidang tambahan dijelaskan - `image_url`. Hanya id-nya yang disimpan di tabel kreatif, dan image_url disimpan di tabel terpisah, hubungan mereka dijelaskan oleh entri lain di tabel LinkRegistry. Dengan demikian, materi iklan ini akan dijelaskan oleh tiga entri, satu di setiap tabel. Karenanya, untuk menyimpan materi iklan seperti itu, Anda harus membuat entri di masing-masingnya, dan untuk membacanya dengan cara yang sama, kunjungi 3 tabel. Akan sangat merepotkan untuk menulis pemrosesan seperti itu setiap saat dari awal, jadi perpustakaan kami mengabstraksikan semua detail ini dari programmer. Untuk bekerja dengan data, Django dan DRF menggunakan model dan serialisator yang dijelaskan oleh kode. Dalam proyek kami, kumpulan bidang dalam model dan serializers ditentukan dalam runtime oleh konfigurasi JSON, kelas model dibuat secara dinamis (menggunakan fungsi tipe) dan disimpan dalam register khusus, dari mana mereka tersedia selama operasi aplikasi. Kami juga menggunakan kelas basis khusus untuk model dan serialis ini, yang membantu dalam bekerja dengan struktur basis non-standar.

Saat menyimpan objek baru (atau memperbarui objek yang sudah ada), data yang diterima dari front-end masuk ke dalam serializer, tempat mereka divalidasi - tidak ada yang tidak biasa, mekanisme DRF standar bekerja. Tetapi menyimpan dan memperbarui didefinisikan ulang di sini. Serializer selalu tahu model mana yang berfungsi, dan sesuai dengan representasi internal model dinamis kami, ia dapat memahami tabel mana dari data bidang selanjutnya yang harus dimasukkan. Kami menyandikan informasi ini dalam bidang model khusus (ingat bagaimana `ForeignKey` dijelaskan dalam Django - model terkait dilewatkan di dalam bidang, kami melakukan hal yang sama). Dalam bidang khusus ini, kami juga abstrak kebutuhan untuk menambahkan catatan mengikat ketiga ke LinkRegistry menggunakan mekanisme deskriptor - dalam kode yang Anda tulis `creative.image_url = 'http: // foo.bar' ', dan dalam metode override __set__ kami menulis ke `LinkRegistry`.
Ini berlaku untuk menulis ke database. Dan sekarang mari kita berurusan dengan membaca. Bagaimana sebuah tuple dikeluarkan dari basis data yang dikonversi menjadi contoh model Django? Dalam model Django dasar, ada metode `from_db`, yang dipanggil untuk setiap tuple yang diterima ketika kueri dijalankan di` queryset`. Pada input, ia menerima tuple dan mengembalikan instance dari model Django. Kami mendefinisikan ulang metode ini dalam model dasar kami, di mana sesuai dengan tuple dari model utama (di mana hanya `id` masuk), kami mendapatkan data dari tabel terkait lainnya dan, setelah set lengkap ini, kami instantiate model. Tentu saja, kami juga berupaya mengoptimalkan mekanisme prefetching Django untuk case use non-standar kami.

Pengujian


Kerangka kerja kami cukup kompleks, jadi kami menulis banyak tes. Kami memiliki tes untuk frontend dan backend. Saya akan membahas tes backend secara detail.

Untuk menjalankan tes, kami menggunakan pytest. Di backend, kami memiliki dua kelas besar tes: tes kerangka kerja kami (kami juga menyebutnya "inti") dan tes proyek.

Dalam kernel, kami menulis kedua unit test yang terisolasi dan fungsional untuk menguji titik akhir menggunakan plugin pytest-django. Secara umum, semua pekerjaan dengan database terutama diuji melalui permintaan ke API - seperti yang terjadi dalam produksi.

Tes fungsional dapat menentukan konfigurasi JSON. Agar tidak melekat pada terminologi desain, kami menggunakan nama "dummy" untuk entitas yang kami uji dengan Fitur kami di kernel ("Emma", "Alla", "Karl", "Maria" dan sebagainya). Karena, dengan menulis fitur image_url, kami tidak ingin membatasi kesadaran pengembang dengan fakta bahwa itu hanya dapat digunakan dengan entitas Kreatif - fitur dan entitas bersifat universal, dan mereka dapat dihubungkan satu sama lain dalam kombinasi yang relevan untuk klien tertentu.

Adapun proyek pengujian, di dalamnya semua kasus pengujian dijalankan dengan konfigurasi produksi, tidak ada entitas tiruan, karena penting bagi kami untuk memeriksa dengan tepat apa yang akan bekerja dengan klien. Dalam proyek tersebut, Anda dapat menulis tes apa pun yang akan mencakup fitur-fitur logika bisnis dari proyek tersebut. Pada saat yang sama, tes CRUD dasar dapat dihubungkan ke proyek dari kernel. Mereka ditulis secara umum, dan mereka dapat dihubungkan ke proyek apa pun: tes fitur dapat membaca konfigurasi JSON suatu proyek, menentukan entitas mana fitur ini terhubung, dan menjalankan pemeriksaan hanya untuk entitas yang diperlukan. Untuk kenyamanan mempersiapkan data uji, kami telah mengembangkan sistem pembantu yang juga dapat menyiapkan set data uji berdasarkan pada konfigurasi JSON. Tempat khusus dalam pengujian proyek ditempati oleh tes E2E pada busur derajat, yang menguji semua fungsi dasar proyek. Tes ini juga dijelaskan menggunakan JSON, yang ditulis dan didukung oleh pengembang front-end.

Kata penutup


Pada artikel ini, kami menguji pendekatan desain modular yang dikembangkan oleh IPONWEB di departemen UI. Solusi ini telah berhasil beroperasi dalam produksi selama tiga tahun. Namun, solusi ini masih memiliki sejumlah keterbatasan yang tidak memungkinkan kita untuk berpuas diri. Pertama, basis kode kami masih cukup kompleks. Kedua, kode dasar yang mendukung model dinamis dikaitkan dengan komponen penting seperti pencarian, pemuatan massal objek, hak akses, dan lainnya. Karena itu, perubahan dalam salah satu komponen dapat secara signifikan mempengaruhi yang lain. Dalam upaya untuk menghilangkan pembatasan ini, kami terus memproses perpustakaan kami secara aktif, memecahnya menjadi banyak bagian independen dan mengurangi kompleksitas kode. Kami akan memberi tahu Anda tentang hasil di artikel berikut.

Artikel ini adalah transkrip tambahan dari presentasi saya di MoscowPythonConf ++ 2019, jadi saya juga membagikan tautan ke video dan slide .

Source: https://habr.com/ru/post/id455720/


All Articles