Backend Ortodoks



Backend modern beragam, tetapi masih mematuhi beberapa aturan yang tak terucapkan. Banyak dari kita yang mengembangkan aplikasi server dihadapkan dengan pendekatan yang diterima secara umum, seperti Arsitektur Bersih, SOLID, Ketidaktahuan Ketekunan, Injeksi Ketergantungan dan lain-lain. Banyak atribut pengembangan server yang sangat usang sehingga tidak menimbulkan pertanyaan dan digunakan tanpa berpikir. Mereka berbicara banyak tentang beberapa, tetapi tidak pernah menggunakannya. Arti dari sisanya adalah salah ditafsirkan atau terdistorsi. Artikel ini berbicara tentang bagaimana membangun arsitektur backend sederhana, benar-benar khas, yang tidak hanya dapat mengikuti sila teori pemrograman terkenal tanpa kerusakan, tetapi juga dapat meningkatkannya sampai batas tertentu.

Didedikasikan untuk semua orang yang tidak berpikir pemrograman tanpa kecantikan dan tidak menerima kecantikan di tengah-tengah absurditas.

Model Domain


Pemodelan adalah tempat pengembangan perangkat lunak di dunia yang ideal harus dimulai. Tetapi kita semua tidak sempurna, kita banyak berbicara tentang itu, tetapi kita melakukan semuanya seperti biasa. Seringkali alasannya adalah ketidaksempurnaan alat yang ada. Dan jujur ​​saja, kemalasan dan ketakutan kita untuk mengambil tanggung jawab untuk menjauh dari "praktik terbaik". Dalam dunia yang tidak sempurna, pengembangan perangkat lunak dimulai, paling-paling, dengan perancah, dan paling buruk, dengan optimalisasi kinerja, tidak ada apa-apa. Namun demikian, saya ingin membuang contoh-contoh sulit dari arsitek "luar biasa" dan berspekulasi pada hal-hal yang lebih biasa.

Jadi, kami memiliki tugas teknis, dan bahkan memiliki desain antarmuka pengguna (atau tidak, jika UI tidak disediakan). Langkah selanjutnya adalah mencerminkan persyaratan dalam model domain. Untuk memulai, Anda dapat membuat sketsa diagram objek model untuk kejelasan:



Kemudian, sebagai suatu peraturan, kita mulai memproyeksikan model pada sarana implementasinya - bahasa pemrograman, konverter objek-relasional (Object-Relational Mapper, ORM), atau pada beberapa jenis kerangka kerja kompleks seperti ASP.NET MVC atau Ruby on Rails, dengan kata lain - mulai menulis kode. Dalam hal ini, kami mengikuti jalur kerangka kerja, yang saya pikir tidak benar dalam kerangka pengembangan berdasarkan model, tidak peduli seberapa nyaman awalnya mungkin terlihat. Di sini Anda membuat asumsi besar, yang kemudian meniadakan manfaat pengembangan berbasis domain. Sebagai pilihan yang lebih bebas, tidak dibatasi oleh ruang lingkup alat apa pun, saya akan menyarankan untuk menggunakan hanya alat sintaksis dari bahasa pemrograman untuk membangun model objek dari area subjek. Dalam pekerjaan saya, saya menggunakan beberapa bahasa pemrograman - C #, JavaScript, Ruby. Takdir telah menetapkan bahwa ekosistem Java dan C # adalah inspirasi saya, JS adalah penghasilan utama saya, dan Ruby adalah bahasa yang saya sukai. Oleh karena itu, saya akan terus menunjukkan contoh sederhana di Ruby: Saya yakin ini tidak akan menyebabkan masalah bagi pengembang dalam bahasa lain untuk mengerti. Jadi, port model ke kelas Faktur di Ruby:

class Invoice attr_reader :amount, :date, :created_at, :paid_at def initialize(attrs, payment_service) @created_at = DateTime.now @paid_at = nil @amount = attrs[:amount] @date = attrs[:date] @subscription = attrs[:subscription] @payment_service = payment_service end def pay credit_card = @subscription.customer.credit_card amount = @subscription.plan.price @payment_service.charge(credit_card, amount) @paid_at = DateTime.now end end 

Yaitu kami memiliki kelas yang konstruktornya menerima Hash atribut, dependensi objek dan menginisialisasi bidangnya, dan metode pembayaran yang dapat mengubah keadaan objek. Semuanya sangat sederhana. Sekarang kita tidak memikirkan bagaimana dan di mana kita akan menampilkan dan menyimpan objek ini. Itu hanya ada, kita dapat membuatnya, mengubah keadaannya, berinteraksi dengan objek lain. Harap dicatat bahwa kode tidak mengandung artefak asing seperti BaseEntity dan sampah lain yang tidak terkait dengan model. Ini sangat penting. Ngomong-ngomong, pada tahap ini kita sudah bisa memulai pengembangan melalui pengujian (TDD), menggunakan objek rintisan alih-alih dependensi seperti payment_service:

 RSpec.describe Invoice do before :each do @payment_service = double(:payment_service) allow(@payment_service).to receive(:charge) @amount = 100 @credit_card = CreditCard.new({...}) @customer = Customer.new({credit_card: @credit_card, ...}) @subscription = Subscription.new({customer: customer, ...}) @invoice = Invoice.new({amount: @amount, date: DateTime.now, @subscription: subscription}, payment_service) end describe 'pay' do it "charges customer's credit card" do expect(@payment_service).to receive(:charge).with(@credit_card, @amount) @invoice.pay end it 'makes the invoice paid' do expect(@invoice.paid_at).not_to be_nil @invoice.pay end end end 

atau bahkan bermain dengan model dalam interpreter (irb untuk Ruby), yang mungkin, meskipun tidak terlalu ramah, antarmuka pengguna:

 irb > invoice = Invoice.new({amount: @amount, date: DateTime.now, @subscription: subscription}, payment_service) irb > invoice.pay 

Mengapa sangat penting untuk menghindari "artefak asing" pada tahap ini? Faktanya adalah bahwa model itu seharusnya tidak tahu bagaimana itu akan disimpan atau apakah itu akan disimpan sama sekali. Pada akhirnya, untuk beberapa sistem penyimpanan objek secara langsung dalam memori mungkin sangat cocok. Pada saat pemodelan, kita harus sepenuhnya abstrak dari detail ini. Pendekatan ini disebut Ketidaktahuan Ketekunan. Harus ditekankan bahwa kita tidak mengabaikan masalah bekerja dengan repositori, apakah itu adalah relasional atau database lain, kita hanya mengabaikan detail berinteraksi dengannya pada tahap pemodelan. Ketidaktahuan Ketekunan berarti penghapusan mekanisme yang disengaja untuk bekerja dengan keadaan model, serta semua jenis metadata yang terkait dengan proses ini, dari model itu sendiri. Contoh:

 #  class User < Entity #     table :users #     # mapping  field :name, type: 'String' #   def save ... end end user = User.load(id) #     user.save #     

 #  class User #   ,      attr_accessor :name, :lastname end user = repo.load(id) #     repo.save(user) #     

Pendekatan ini juga karena alasan mendasar - kepatuhan dengan prinsip tanggung jawab tunggal (Prinsip Tanggung Jawab Tunggal, S dalam SOLID). Jika model, di samping komponen fungsionalnya, menjelaskan parameter pelestarian keadaan dan juga berkaitan dengan pelestarian dan pemuatannya, maka jelas ia memiliki terlalu banyak tanggung jawab. Hasil dan bukan keuntungan terakhir dari Ketidaktahuan Ketekunan adalah kemampuan untuk mengganti alat penyimpanan dan bahkan jenis penyimpanan itu sendiri selama proses pengembangan.

Model-View-Controller


Konsep MVC sangat populer di lingkungan pengembangan berbagai aplikasi, tidak hanya server, dalam berbagai bahasa dan platform yang tidak lagi kita pikirkan tentang apa itu dan mengapa itu diperlukan sama sekali. Saya memiliki sebagian besar pertanyaan dari singkatan ini yang disebut "Controller". Dari sudut pandang pengorganisasian struktur kode, merupakan hal yang baik untuk mengelompokkan tindakan pada model. Tetapi controller tidak boleh kelas sama sekali, itu harus lebih merupakan modul yang mencakup metode untuk mengakses model. Tidak hanya itu, haruskah tempat itu ada? Sebagai pengembang yang mengikuti jalur .NET -> Ruby -> Node.js, saya cukup tersentuh oleh pengontrol JS (ES5) yang menerapkan dalam kerangka express.js. Memiliki kemampuan untuk menyelesaikan tugas yang diberikan kepada pengendali dengan gaya yang lebih fungsional, para pengembang, seperti yang disihir, menulis "Controller" ajaib berulang kali. Mengapa pengontrol biasa buruk?

Pengontrol tipikal adalah seperangkat metode yang tidak terkait erat satu sama lain, disatukan oleh hanya satu - esensi tertentu dari model; dan terkadang bukan hanya satu, lebih buruk. Setiap metode individual mungkin memerlukan dependensi yang berbeda. Melihat ke depan sedikit, saya perhatikan bahwa saya adalah pendukung praktik inversi ketergantungan (Dependency Inversion, D in SOLID). Oleh karena itu, saya perlu menginisialisasi dependensi ini di suatu tempat di luar dan meneruskannya ke konstruktor controller. Misalnya, ketika membuat akun baru, saya harus mengirim notifikasi ke akuntan, yang mana saya memerlukan layanan notifikasi, dan dengan metode lain saya tidak memerlukannya:

 class InvoiceController def initialize(invoice_repository, notification_service) @repository = invoice_repository @notification_service = notification_service end def index @repository.get_all end def show(id) @repository.get_by_id(id) end def create(data) @repository.create(data) @notification_service.notify_accountant end end 

Di sini ide tersebut ingin dibagi menjadi metode untuk bekerja dengan model ke dalam kelas terpisah, dan mengapa tidak?

 class ListInvoices def initialize(invoice_repository) @repository = invoice_repository end def call @repository.get_all end end class CreateInvoice def initialize(invoice_repository, notification_service) @repository = invoice_repository @notification_service = notification_service end def call @repository.create(data) @notification_service.notify_accountant end end 

Nah, alih-alih controller, sekarang ada satu set "fungsi" untuk mengakses model, yang, omong-omong, juga dapat disusun menggunakan direktori sistem file, misalnya. Sekarang Anda perlu "membuka" metode ini ke luar, mis. mengatur sesuatu seperti router. Sebagai orang yang tergoda dengan semua jenis DSL (Domain-Specific Language), saya lebih suka untuk memiliki deskripsi yang lebih visual dari instruksi untuk aplikasi web daripada trik dalam Ruby atau bahasa tujuan umum lainnya untuk menentukan rute:

 `HTTP GET /invoices -> return all invoices` `HTTP POST /invoices -> create new invoice` 

atau setidaknya

 `HTTP GET /invoices -> ./invoices/list_invoices` `HTTP POST /invoices -> ./invoices/create` 

Ini sangat mirip dengan Router biasa, dengan satu-satunya perbedaan adalah bahwa ia tidak berinteraksi dengan pengendali, tetapi langsung dengan tindakan pada model. Jelas bahwa jika kita ingin mengirim dan menerima JSON, maka kita harus mengurus serialisasi dan deserialisasi objek dan banyak lagi. Dengan satu atau lain cara, kita dapat menyingkirkan pengontrol, mengalihkan sebagian tanggung jawab mereka ke struktur direktori dan Router yang lebih canggih.

Ketergantungan injeksi


Saya sengaja menulis "Router yang lebih canggih." Agar router benar-benar dapat memungkinkan aliran tindakan pada model menggunakan mekanisme injeksi dependensi pada tingkat deklaratif, itu mungkin harus cukup rumit di dalamnya. Skema umum karyanya akan terlihat seperti ini:



Seperti yang Anda lihat, seluruh router saya penuh dengan injeksi ketergantungan menggunakan wadah IoC. Mengapa ini perlu? Konsep "injeksi ketergantungan" kembali ke teknik Ketergantungan Inversi, yang dirancang untuk mengurangi konektivitas objek dengan memindahkan inisialisasi dependensi di luar ruang lingkup penggunaannya. Contoh:

 class Repository; end #  (   ) class A def initialize @repo = Repository.new end end #  (   ) class A def initialize(repo) @repo = repo end end 

Pendekatan ini sangat membantu mereka yang menggunakan Test-Driven Development. Dalam contoh di atas, kita dapat dengan mudah meletakkan sebuah rintisan di konstruktor alih-alih objek repositori nyata sesuai dengan antarmuka, tanpa "meretas" model objek. Ini bukan satu-satunya bonus DI: ketika diterapkan dengan benar, pendekatan ini akan membawa banyak keajaiban yang menyenangkan ke aplikasi Anda, tetapi hal pertama yang pertama. Dependency Injection adalah pendekatan yang memungkinkan Anda untuk mengintegrasikan teknik Ketergantungan Inversi ke dalam solusi arsitektur yang lengkap. Alat implementasi biasanya merupakan wadah IoC- (Inversion of Control). Ada banyak wadah IoC yang sangat keren di dunia Java dan .NET, ada puluhan. Di JS dan Ruby, sayangnya, tidak ada opsi yang cocok untuk saya. Secara khusus, saya melihat dry-container (wadah kering ). Ini akan menjadi seperti apa kelas saya akan terlihat seperti menggunakannya:

 class Invoice include Import['payment_service'] def pay credit_card = @subscription.customer.credit_card amount = @subscription.plan.price @payment_service.charge(credit_card, amount) end end 

Alih-alih menggunakan konstruktor yang lebih ramping, kami membebani kelas dengan memperkenalkan dependensi kami sendiri, yang pada tahap awal membawa kami menjauh dari model yang bersih dan mandiri. Nah, sesuatu dan modelnya seharusnya tidak tahu sama sekali tentang IoC! Ini berlaku untuk tindakan seperti CreateInvoice. Untuk kasus yang diberikan, dalam pengujian saya, saya sudah wajib menggunakan IOC sebagai sesuatu yang tidak dapat dicabut. Ini benar-benar salah. Objek aplikasi untuk sebagian besar tidak boleh tahu tentang keberadaan IoC. Setelah mencari dan berpikir banyak, saya membuat sketsa IoC saya , yang tidak akan terlalu mengganggu.

Menyimpan dan memuat model


Ketidaktahuan Ketekunan membutuhkan transformator objek yang tidak mencolok. Dalam artikel ini, maksud saya akan bekerja dengan database relasional, poin utama akan berlaku untuk jenis penyimpanan lainnya. Konverter objek-relasional - ORM (Object Relational Mapper) digunakan sebagai konverter serupa untuk database relasional. Di dunia .NET dan Java, ada banyak alat ORM yang benar-benar kuat. Semua dari mereka memiliki beberapa atau kekurangan kecil lain yang dapat Anda tutup mata. Tidak ada solusi yang baik di JS dan Ruby. Semua dari mereka, dengan satu atau lain cara, dengan kaku mengikat model ke kerangka kerja dan memaksa deklarasi elemen asing, belum lagi ketidakmampuan Ketidaktahuan Ketekunan. Seperti dalam kasus IoC, saya berpikir tentang menerapkan ORM sendiri, ini adalah keadaan di Ruby. Saya tidak melakukan semuanya dari awal, tetapi mengambil sebagai sekuel ORM sederhana, yang menyediakan alat yang tidak mencolok untuk bekerja dengan berbagai DBMS relasional. Pertama-tama, saya tertarik pada kemampuan untuk mengeksekusi query dalam bentuk SQL biasa, menerima array string (objek hash) pada output. Tinggal menerapkan Mapper Anda dan memberikan Ketidaktahuan Ketekunan. Seperti yang sudah saya sebutkan, saya tidak ingin mencampur bidang pemetaan ke dalam model domain, jadi saya mengimplementasikan Mapper sehingga menggunakan file konfigurasi terpisah dalam format tipe:

 entity Invoice do field :amount field :date field :start_date field :end_date field :created_at field :updated_at reference :user, type: User reference :subscription, type: Subscription end 

Ketidaktahuan Ketekunan cukup sederhana untuk diterapkan menggunakan objek eksternal dari tipe Repositori:

 repository.save(user) 

Namun kami akan melangkah lebih jauh dan menerapkan pola Unit Kerja. Untuk melakukan ini, Anda perlu menyoroti konsep sesi. Sesi adalah objek yang ada dari waktu ke waktu, di mana serangkaian tindakan dilakukan pada model, yang merupakan operasi logis tunggal. Selama sesi berlangsung, pemuatan dan perubahan objek model dapat terjadi. Pada akhir sesi, keadaan transaksional model disimpan.
Unit kerja contoh:

 user = session.load(User, id: 1) plan = session.load(Plan, id: 1) subscription = Subscription.new(user, plan) session.attach(subscription) invoice = Invoice.new(subscription) session.attach(invoice) # ... # -       if Date.today.yday == 1 subscription.comment = 'New year offer' invoice.amount /= 2 end session.flush 

Akibatnya, 2 instruksi akan dieksekusi dalam database, bukan 4, dan keduanya akan dieksekusi dalam transaksi yang sama.

Dan tiba-tiba ingat repositori! Di sini ada perasaan deja vu, seperti halnya dengan pengontrol: bukankah repositori merupakan entitas yang belum sempurna? Ke depan, saya akan menjawab - ya, benar. Tujuan utama repositori adalah untuk menyelamatkan lapisan logika bisnis agar tidak berinteraksi dengan penyimpanan nyata. Misalnya, dalam konteks basis data relasional, itu berarti menulis query SQL langsung dalam kode logika bisnis. Tidak diragukan lagi, ini adalah keputusan yang sangat masuk akal. Tetapi kembali ke saat ketika kita menyingkirkan controller. Dari sudut pandang OOP, repositori pada dasarnya adalah pengontrol yang sama - serangkaian metode yang sama, tidak hanya untuk memproses permintaan, tetapi untuk bekerja dengan repositori. Repositori juga dapat dibagi menjadi beberapa tindakan. Dengan semua indikasi, tindakan ini tidak akan berbeda dengan apa pun dari apa yang kami usulkan daripada controller. Yaitu, kita dapat menolak Repositori dan Pengendali demi satu Tindakan tunggal!

 class LoadPlan def initialize(session) @session = session end def call sql = <<~SQL SELECT p.* AS ENTITY plan FROM plans p WHERE p.id = 1 SQL @session.fetch(Plan, sql) end end 

Anda mungkin memperhatikan bahwa saya menggunakan SQL alih-alih semacam sintaks objek. Ini masalah selera. Saya lebih suka SQL karena ini adalah bahasa query, semacam DSL untuk bekerja dengan data. Jelas bahwa selalu lebih mudah untuk menulis Plan.load (id) daripada SQL yang sesuai, tetapi ini untuk kasus-kasus sepele. Ketika datang ke hal-hal yang sedikit lebih kompleks, SQL menjadi alat yang sangat disambut. Kadang-kadang Anda mengutuk ORM lain dalam mencoba membuatnya seperti SQL murni, yang "Saya akan menulis dalam beberapa menit." Bagi mereka yang ragu, saya sarankan untuk melihat dokumentasi MongoDB , di mana penjelasannya diberikan dalam bentuk seperti SQL, yang terlihat sangat lucu! Oleh karena itu, antarmuka untuk kueri dalam JetSet ORM , yang saya tulis untuk tujuan saya, adalah SQL dengan impregnasi minimal seperti "SEBAGAIMANA ENTITAS". Ngomong-ngomong, dalam kebanyakan kasus saya tidak menggunakan objek model, berbagai DTO, dll. Untuk menampilkan data tabular - Saya hanya menulis kueri SQL, mendapatkan array objek hash dan menampilkannya dalam tampilan. Dengan satu atau lain cara, beberapa orang berhasil "menggulir" data besar dengan memproyeksikan tabel terkait ke model. Dalam praktiknya, proyeksi rata (tampilan) lebih mungkin digunakan, dan produk yang sangat matang sampai pada tahap optimasi ketika solusi yang lebih kompleks seperti CQRS (Command and Query Responsibility Segregation) mulai digunakan.

Menyatukan semuanya


Jadi apa yang kita miliki:

  • kami menemukan cara memuat dan menyimpan model, kami juga merancang arsitektur kasar alat pengiriman web model, router tertentu;
  • kami sampai pada kesimpulan bahwa semua logika yang bukan bagian dari area subjek dapat dibawa ke Tindakan (Actions), bukan pengendali dan repositori;
  • Tindakan harus mendukung injeksi ketergantungan
  • alat yang layak Injeksi Ketergantungan dilaksanakan;
  • ORM yang diperlukan diimplementasikan.

Satu-satunya yang tersisa adalah mengimplementasikan "router" yang sama. Karena kita menyingkirkan repositori dan pengontrol yang mendukung tindakan, jelas bahwa untuk satu permintaan kita perlu melakukan beberapa tindakan. Tindakan bersifat otonom dan kami tidak dapat saling berinvestasi. Oleh karena itu, sebagai bagian dari kerangka kerja Dandy, saya mengimplementasikan router yang memungkinkan Anda membuat rantai tindakan. Contoh konfigurasi (perhatikan / rencana):

 :receive .-> :before -> common/open_db_session GET -> welcome -> :respond <- show_welcome /auth -> :before -> current_user@users/load_current_user /profile -> GET -> plan@plans/load_plan \ -> :respond <- users/show_user_profile PATCH -> users/update_profile /plans -> GET -> current_plan@plans/load_current_plan \ -> plans@plans/load_plans \ -> :respond <- plans/list :catch -> common/handle_errors 

"DAPATKAN / auth / paket" menampilkan semua paket berlangganan yang tersedia dan "menyoroti" yang sekarang. Berikut ini terjadi:

  1. ": before -> common / open_db_session" - membuka sesi JetSet
  2. / auth ": before -> current_user @ users / load_current_user" - memuat pengguna saat ini (berdasarkan token). Hasilnya dicatat dalam wadah IoC sebagai current_user (current_user @ instruksi).
  3. / auth / plan "current_plan @ plan / load_current_plan" - muat paket saat ini. Untuk ini, nilai @current_user diambil dari wadah. Hasilnya dicatat dalam wadah IoC sebagai current_plan (current_plan @ instruksi):

     class LoadCurrentPlan def initialize(current_user, session) @current_user = current_user @session = session end def call sql = <<~SQL SELECT p.* AS ENTITY plan FROM plans p INNER JOIN subscriptions s ON s.user_id = :user_id AND s.current = 't' WHERE p.id = :user_id LIMIT 1 SQL @session.execute(sql, user_id: @current_user.id) do |row| map(Plan, row, 'plan') end end end 

  4. "Plans @ plan / load_plans" - memuat daftar semua paket yang tersedia. Hasilnya terdaftar dalam wadah IoC sebagai paket (paket rencana @ instruksi).
  5. ": balas <- paket / daftar" - ViewBuilder terdaftar, misalnya JBuilder, menggambar Lihat 'paket / rencana' dari jenis:

     json.plans @plans do |plan| json.id plan.id json.name plan.name json.price plan.price json.active plan.id == @current_plan.id end 


Sebagai @plans dan @current_plan, nilai yang terdaftar di langkah sebelumnya diambil dari wadah. Dalam konstruktor Aksi, secara umum, Anda dapat "memesan" semua yang Anda butuhkan, atau lebih tepatnya, semua yang terdaftar dalam wadah. Seorang pembaca yang penuh perhatian kemungkinan besar akan memiliki pertanyaan, tetapi apakah ada isolasi variabel tersebut dalam mode "multi-pengguna"? Ya itu. Faktanya adalah bahwa wadah Hypo IoC memiliki kemampuan untuk mengatur masa pakai objek dan, apalagi, mengikatnya dengan masa pakai objek lain. Di dalam Dandy, variabel-variabel seperti @plans, @current_plan, @current_user terikat ke objek permintaan dan akan dimusnahkan saat permintaan selesai. Ngomong-ngomong, sesi JetSet juga terikat dengan permintaan - pengaturan ulang negaranya juga akan dilakukan ketika permintaan Dandy selesai. Yaitu Setiap permintaan memiliki konteks tersendiri. Hypo memerintah seluruh siklus hidup Dandy, tidak peduli betapa menyenangkan pun permainan ini dalam terjemahan harfiah nama-nama itu.

Kesimpulan


Dalam kerangka arsitektur yang diberikan, saya menggunakan model objek untuk menggambarkan area subjek; Saya menggunakan praktik yang sesuai seperti Injeksi Ketergantungan; Saya bahkan bisa menggunakan warisan. Tetapi, pada saat yang sama, semua Tindakan ini pada dasarnya adalah fungsi biasa yang dapat dirantai bersama pada tingkat deklaratif. Kami mendapatkan backend yang diinginkan dalam gaya fungsional, tetapi dengan semua kelebihan dari pendekatan objek, ketika Anda tidak mengalami masalah dengan abstraksi dan menguji kode Anda. Menggunakan router DSL Dandy sebagai contoh, kita bebas membuat bahasa yang diperlukan untuk menggambarkan rute dan banyak lagi.

Kesimpulan


Sebagai bagian dari artikel ini, saya melakukan semacam kunjungan pada aspek-aspek mendasar dari menciptakan backend seperti yang saya lihat. Saya ulangi, artikel ini dangkal, tidak menyentuh banyak topik penting, seperti, misalnya, pengoptimalan kinerja. Saya mencoba untuk fokus hanya pada hal-hal yang benar-benar dapat bermanfaat bagi masyarakat sebagai makanan untuk dipikirkan, dan tidak sekali lagi menuangkan dari kosong ke kosong, apa yang SOLID, TDD, bagaimana skema MVC terlihat, dan sebagainya. Definisi yang ketat tentang ini dan istilah-istilah lain yang digunakan oleh pembaca yang ingin tahu dapat dengan mudah ditemukan di jaringan yang luas, belum lagi kolega di toko, untuk siapa singkatan ini merupakan bagian dari pidato sehari-hari. Dan akhirnya, saya tekankan, cobalah untuk tidak fokus pada alat yang perlu saya implementasikan untuk menyelesaikan masalah yang diajukan.Ini hanya demonstrasi dari validitas pikiran, bukan esensi mereka. Jika artikel ini menarik, saya akan menulis bahan terpisah tentang perpustakaan ini.

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


All Articles