Dalam melakukannya, mereka lupa bahwa pola hanyalah solusi yang mungkin. Pola, seperti prinsip lainnya, memiliki batasan penerapan, dan penting untuk memahaminya. Jalan menuju neraka dilapisi dengan ketaatan buta dan religius bahkan pada kata-kata yang berwibawa.
Dan kehadiran pola yang diperlukan dalam kerangka kerja tidak menjamin penerapannya secara benar dan sadar.

Gemerlap dan kemiskinan Rekaman Aktif
Mari kita lihat pola Rekaman Aktif sebagai anti-pola, yang beberapa bahasa pemrograman dan kerangka kerja coba hindari dengan segala cara yang memungkinkan.
Inti dari Rekaman Aktif sederhana: kami menyimpan logika bisnis dengan logika penyimpanan entitas. Dengan kata lain, sederhananya, setiap tabel dalam database sesuai dengan kelas entitas bersama dengan perilaku.

Ada pendapat yang cukup kuat bahwa menggabungkan logika bisnis dengan logika penyimpanan dalam satu kelas adalah pola yang sangat buruk dan tidak dapat digunakan. Itu melanggar prinsip tanggung jawab tunggal. Dan karena alasan ini Django ORM secara sengaja buruk.
Memang, mungkin tidak terlalu baik untuk menggabungkan logika penyimpanan dan logika domain di kelas yang sama.
Mari kita ambil model Pengguna dan Profil sebagai contoh. Ini adalah pola yang cukup umum. Ada pelat utama, dan ada pelat tambahan, yang menyimpan tidak selalu wajib, tetapi terkadang data yang diperlukan.

Ternyata entitas domain "pengguna" sekarang disimpan dalam dua tabel, dan dalam kode kita memiliki dua kelas. Dan setiap kali kami secara langsung membuat beberapa koreksi
user.profile, kami perlu ingat bahwa ini adalah model yang terpisah dan kami membuat perubahan di dalamnya. Dan simpan secara terpisah.
def create(self, validated_data):
# create user
user = User.objects.create(
url = validated_data['url'],
email = validated_data['email'],
# etc ...
)
profile_data = validated_data.pop('profile')
# create profile
profile = Profile.objects.create(
user = user
first_name = profile_data['first_name'],
last_name = profile_data['last_name'],
# etc...
)
return user
Untuk mendapatkan daftar pengguna, sangat penting untuk memikirkan apakah atribut akan diambil dari pengguna ini
profileuntuk segera memilih dua tanda dengan gabungan dan tidak mendapatkannya SELECT N+1dalam satu lingkaran.
user = User.objects.get(email='example@examplemail.com')
user.userprofile.company_name
user.userprofile.country
Keadaan menjadi lebih buruk jika, dalam arsitektur layanan mikro, bagian dari data pengguna disimpan di layanan lain - misalnya, peran dan hak di LDAP.
Pada saat yang sama, tentu saja, saya benar-benar tidak ingin pengguna eksternal API peduli tentang hal ini. Ada sumber daya REST
/users/{user_id}, dan saya ingin bekerja dengannya tanpa memikirkan bagaimana data disimpan di dalamnya. Jika mereka disimpan di sumber yang berbeda, maka mengubah pengguna atau mengambil daftar data akan lebih sulit.
Secara umum, ORM! = Model Domain!

Dan semakin dunia nyata berbeda dari asumsi "satu tabel dalam database - satu entitas domain," semakin banyak masalah dengan pola Rekaman Aktif.
Ternyata setiap menulis business logic pasti ingat bagaimana esensi domain disimpan.
Metode ORM adalah tingkat abstraksi terendah. Mereka tidak mendukung batasan bidang pelajaran, yang berarti mereka memberikan kesempatan untuk membuat kesalahan. Mereka juga menyembunyikan dari pengguna kueri apa yang sebenarnya dibuat dalam database, yang mengarah ke kueri yang tidak efektif dan panjang. Klasik, ketika kueri dibuat dalam loop, bukan gabungan atau filter.
Dan apa lagi, selain dari querybuilding (kemampuan untuk membuat kueri), yang diberikan ORM kepada kita? Sudahlah. Kemampuan untuk pindah ke database baru? Dan siapa yang waras dan ingatan kuat yang pindah ke database baru dan ORM membantunya dalam hal ini? Jika Anda menganggapnya bukan sebagai upaya untuk memetakan model domain (!) Ke dalam database, tetapi sebagai pustaka sederhana yang memungkinkan Anda membuat kueri ke database dengan cara yang nyaman, maka semuanya akan berada pada tempatnya.
Dan meskipun mereka digunakan dalam nama kelas
Model, dan dalam nama file models, mereka tidak menjadi model. Jangan menipu diri sendiri. Itu hanya deskripsi label. Mereka tidak akan membantu merangkum apa pun.
Tetapi jika semuanya sangat buruk, lalu apa yang harus dilakukan? Pola dari arsitektur berlapis datang untuk menyelamatkan.
Arsitektur berlapis menyerang balik!
Ide di balik arsitektur berlapis sederhana: kami memisahkan logika bisnis, logika penyimpanan, dan logika penggunaan.
Tampaknya sangat logis untuk memisahkan penyimpanan dari perubahan status. Itu. buat layer terpisah yang dapat menerima dan menyimpan data dari penyimpanan "abstrak".
Kami meninggalkan semua logika penyimpanan, misalnya, di kelas penyimpanan
Repository. Dan pengontrol (atau lapisan layanan) hanya menggunakannya untuk mendapatkan dan menyimpan entitas. Kemudian kita dapat mengubah logika menyimpan dan menerima sesuka kita, dan ini akan menjadi satu tempat! Dan ketika kita menulis kode klien, kita dapat yakin bahwa kita tidak melupakan satu tempat lagi di mana kita perlu menyimpan atau dari mana kita perlu mengambilnya, dan kita tidak mengulangi kode yang sama beberapa kali.

Tidak masalah bagi kami jika entitas terdiri dari rekaman dalam tabel atau layanan mikro yang berbeda. Atau jika entitas dengan perilaku berbeda tergantung pada tipenya disimpan dalam satu tabel.
Tetapi pembagian tanggung jawab ini tidak gratis . Harus dipahami bahwa lapisan abstraksi tambahan dibuat untuk mencegah perubahan kode yang "buruk". Jelas, ini
Repositorymenyembunyikan fakta bahwa objek disimpan dalam database SQL, jadi Anda harus mencoba untuk tidak membiarkan SQLism keluar batas Repository. Dan semua permintaan, bahkan yang paling sederhana dan jelas, harus ditarik melalui lapisan penyimpanan.
Misalnya, jika perlu mendapatkan kantor berdasarkan nama dan departemen, Anda harus menulis seperti ini:
#
interface OfficeRepository: CrudRepository<OfficeEntity, Long> {
@Query("select o from OfficeEntity o " +
"where o.number = :office and o.branch.number = :branch")
fun getOffice(@Param("branch") branch: String,
@Param("office") office: String): OfficeEntity?
...
Dan dalam kasus Rekaman Aktif, semuanya jauh lebih sederhana:
Office.objects.get(name=’Name’, branch=’Branch’)
Tidak sesederhana itu meskipun entitas bisnis sebenarnya disimpan dengan cara yang tidak sepele (di beberapa tabel, di layanan yang berbeda, dll.). Untuk mengimplementasikan ini dengan baik (dan benar) - di mana pola ini dibuat - paling sering Anda harus menggunakan pola seperti agregat, Unit kerja, dan Pemeta data.
Sulit untuk memilih agregat dengan benar, mengamati dengan benar semua batasan yang diberlakukan padanya, dan membuat pemetaan data dengan benar. Dan hanya pengembang yang sangat baik yang dapat mengatasi tugas ini. Salah satu yang, dalam kasus Rekaman Aktif, bisa melakukan segalanya dengan "benar".
Apa yang terjadi pada pengembang biasa? Mereka yang mengetahui semua pola dan sangat yakin bahwa jika mereka menggunakan arsitektur berlapis, maka kode mereka secara otomatis menjadi dapat dipelihara dan baik, tidak seperti Rekaman Aktif. Dan mereka membuat repositori CRUD untuk setiap tabel. Dan mereka bekerja dalam konsep
satu pelat - satu gudang - satu entitas.
Bukan:
satu repositori - satu objek domain.

Mereka juga secara membabi buta percaya bahwa jika sebuah kata digunakan di kelasEntity, itu mencerminkan model domain. Seperti sebuah kataModeldalam Rekaman Aktif.
Hasilnya adalah lapisan penyimpanan yang lebih kompleks dan kurang fleksibel yang memiliki semua properti negatif dari Active Record dan Repository / Data mappers.
Tetapi arsitektur berlapis tidak berakhir di situ. Lapisan layanan biasanya juga dibedakan.
Implementasi yang benar dari lapisan layanan semacam itu juga merupakan tugas yang sulit. Dan, misalnya, pengembang yang tidak berpengalaman membuat lapisan layanan, yang merupakan layanan - proxy ke repositori atau ORM (DAO). Itu. layanan ditulis sehingga tidak benar-benar merangkum logika bisnis:
#
@Service
class AccountServiceImpl(val accountDaoService: AccountDaoService) : AccountService {
override fun saveAccount(account: Account) =
accountDaoService.saveAccount(convertClass(account, AccountEntity::class.java))
override fun deleteAccount(id: Long) =
accountDaoService.deleteAccount(id)
Dan ada kombinasi kelemahan dari layer Rekaman Aktif dan Layanan.
Akibatnya, dalam kerangka kerja Java berlapis dan kode yang ditulis oleh pecinta pola yang muda dan tidak berpengalaman, jumlah abstraksi per unit logika bisnis mulai melebihi semua batas yang wajar.

Ada beberapa lapisan, tetapi semuanya sepele dan hanya lapisan untuk memanggil lapisan berikutnya.
Kehadiran pola OOP dalam kerangka tidak menjamin penerapannya secara benar dan memadai.
Tidak ada peluru perak
Sangat jelas bahwa tidak ada peluru perak. Solusi kompleks untuk masalah kompleks, dan solusi sederhana untuk masalah sederhana.
Dan tidak ada pola yang baik dan buruk. Dalam satu situasi, Rekaman Aktif bagus, dalam situasi lain, arsitektur berlapis. Dan ya, untuk sebagian besar aplikasi berukuran kecil hingga menengah, Rekaman Aktif berfungsi dengan baik. Dan untuk sebagian besar aplikasi kecil dan menengah, arsitektur berlapis (a la Spring) berkinerja lebih buruk. Dan justru sebaliknya untuk aplikasi kompleks dan layanan web yang kaya logika.
Semakin sederhana aplikasi atau layanan, semakin sedikit lapisan abstraksi yang Anda butuhkan.
Dalam layanan mikro, di mana tidak banyak logika bisnis, sering kali tidak ada gunanya menggunakan arsitektur berlapis. Skrip transaksional biasa - skrip dalam pengontrol - mungkin cukup memadai untuk tugas yang dihadapi.
Sebenarnya, pengembang yang baik berbeda dari yang buruk karena ia tidak hanya mengetahui polanya, tetapi juga memahami kapan harus menerapkannya.