Pendahuluan
Jadi, kami telah memutuskan ruang lingkup , metodologi dan arsitektur . Mari kita beralih dari teori ke praktik, ke menulis kode. Saya ingin memulai dengan pola desain yang menggambarkan logika bisnis - Layanan dan Interaktor . Tetapi sebelum memulai, kita akan memeriksa pola struktural - ValueObject dan Entity . Kami akan berkembang dalam bahasa ruby . Dalam artikel selanjutnya, kami akan menganalisis semua pola yang diperlukan untuk pengembangan menggunakan Arsitektur Variabel . Semua pengembangan yang merupakan aplikasi untuk seri artikel ini akan dikumpulkan dalam kerangka kerja yang terpisah.

Dan kami telah mengambil nama yang cocok - LunaPark .
Perkembangan saat ini diposting di Github .
Setelah memeriksa semua templat, kami akan merakit satu layanan microsoft lengkap.
Jadi secara historis
Ada kebutuhan untuk memperbaiki aplikasi perusahaan yang rumit yang ditulis dalam Ruby on Rails. Ada tim pengembang ruby ββyang sudah jadi. Metodologi Pengembangan Domain Driven sempurna untuk tugas-tugas ini, tetapi tidak ada solusi turnkey dalam bahasa yang digunakan. Terlepas dari kenyataan bahwa pilihan bahasa terutama ditentukan oleh spesialisasi kami, ternyata cukup berhasil. Di antara semua bahasa yang biasa digunakan untuk aplikasi web, ruby, menurut saya, adalah yang paling ekspresif. Dan karenanya, lebih dari yang lain cocok untuk memodelkan objek nyata. Ini bukan hanya pendapat saya.
Itu adalah dunia Jawa. Maka Anda memiliki pendatang baru seperti Ruby. Ruby memiliki sintaks yang sangat ekspresif, dan pada level dasar ini seharusnya merupakan bahasa yang sangat baik untuk DDD (walaupun saya belum pernah mendengar banyak penggunaan aktualnya dalam aplikasi semacam itu). Rails telah menghasilkan banyak kegembiraan karena akhirnya membuat pembuatan UI Web semudah UI kembali pada awal 1990-an, sebelum Web. Saat ini, kemampuan ini sebagian besar telah diterapkan untuk membangun sejumlah besar aplikasi Web yang tidak memiliki banyak kekayaan domain di belakangnya, karena bahkan ini sangat sulit di masa lalu. Tetapi harapan saya adalah bahwa, ketika bagian implementasi UI dari masalah berkurang, orang-orang akan melihat ini sebagai kesempatan untuk lebih memfokuskan perhatian mereka pada domain. Jika penggunaan Ruby mulai mengarah ke sana, saya pikir itu bisa menyediakan platform yang sangat baik untuk DDD. (Beberapa infrastruktur mungkin harus diisi.)
Eric Evans 2006
Sayangnya, selama 13 tahun terakhir, tidak ada yang berubah banyak. Di Internet Anda dapat menemukan upaya untuk mengadaptasi Rails untuk ini, tetapi semuanya terlihat mengerikan. Kerangka Rails berat, lambat, dan tidak SOLID. Sangat sulit untuk menonton tanpa air mata bagaimana seseorang mencoba menggambarkan implementasi pola Repositori berdasarkan ActiveRecord . Kami memutuskan untuk mengadopsi mikro dan memodifikasinya untuk kebutuhan kami. Kami mencoba Grape , gagasan dengan dokumentasi otomatis sepertinya berhasil, tetapi jika tidak ditinggalkan dan kami dengan cepat meninggalkan gagasan untuk menggunakannya. Dan segera mereka mulai menggunakan solusi lain - Sinatra . Kami masih terus menggunakannya untuk Pengendali dan Titik Akhir REST.
SISA?Jika Anda mengembangkan aplikasi web, maka Anda sudah memiliki gagasan tentang teknologi tersebut. Ini memiliki pro dan kontra, daftar lengkap yang berada di luar cakupan artikel ini. Tetapi bagi kami, sebagai pengembang aplikasi perusahaan, kelemahan terpenting adalah bahwa REST (ini jelas bahkan dari namanya) tidak mencerminkan prosesnya, melainkan keadaannya. Dan keuntungannya adalah dapat dimengerti - teknologinya jelas bagi pengembang back-end dan front-end.
Tapi kemudian mungkin tidak fokus pada REST, tetapi mengimplementasikan solusi http + json Anda? Bahkan jika Anda berhasil mengembangkan API layanan Anda, kemudian memberikan deskripsinya kepada pihak ketiga Anda akan menerima banyak pertanyaan. Jauh lebih daripada jika Anda memberikan REST yang akrab.
Kami akan mempertimbangkan untuk menggunakan REST sebagai solusi kompromi. Kami menggunakan JSON untuk keringkasan dan standar jsonapi agar tidak membuang waktu pengembang pada perang suci mengenai format permintaan.
Di masa depan, ketika kita menganalisis Endpoint , kita akan melihat bahwa untuk menghilangkan istirahat, cukup menulis ulang hanya satu kelas. Jadi REST tidak perlu repot sama sekali jika ada keraguan tentang hal itu.
Dalam perjalanan menulis beberapa layanan microser, kami telah memperoleh dasar - satu set kelas abstrak. Setiap kelas dapat ditulis dalam setengah jam, kodenya mudah dimengerti jika Anda tahu untuk apa kode ini.
Di sini kesulitan utama muncul. Karyawan baru yang tidak berurusan dengan praktik DDD dan arsitektur bersih tidak dapat memahami kode dan tujuannya. Jika saya sendiri melihat kode ini untuk pertama kalinya sebelum membaca Evans, saya akan menganggapnya sebagai warisan, rekayasa berlebihan.
Untuk mengatasi kendala ini, diputuskan untuk menulis dokumentasi (pedoman) yang menggambarkan filosofi dari pendekatan yang digunakan. Garis besar dokumentasi ini tampaknya berhasil dan diputuskan untuk menempatkannya di HabrΓ©. Kelas abstrak yang diulang dari proyek ke proyek, diputuskan untuk dimasukkan ke dalam permata yang terpisah.
Filsafat

Jika Anda mengingat beberapa film klasik tentang seni bela diri, maka akan ada seorang pria keren yang sangat cekatan menangani tiang. Yang keenam pada dasarnya adalah tongkat, alat yang sangat primitif, salah satu yang pertama jatuh ke tangan manusia. Namun di tangan tuannya, ia menjadi senjata yang tangguh.
Anda dapat menghabiskan waktu membuat pistol yang tidak menembak kaki Anda, atau Anda dapat menghabiskan waktu mempelajari teknik menembak. Kami telah mengidentifikasi 4 prinsip dasar:
- Anda perlu membuat hal-hal rumit menjadi sederhana.
- Pengetahuan lebih penting daripada teknologi. Dokumentasi lebih mudah dipahami seseorang daripada kode, seseorang tidak boleh mengganti yang satu dengan yang lain.
- Pragmatisme lebih penting daripada dogmatisme. Standar harus memandu jalan, bukan menetapkan kotak pembatas.
- Strukturalitas dalam arsitektur, fleksibilitas dalam pilihan solusi.
Filosofi serupa dapat ditelusuri, misalnya, di ArchLinux OS - The Arch Way . Di laptop saya, Linux tidak berakar untuk waktu yang lama, cepat atau lambat ia rusak dan saya harus menginstalnya kembali. Ini menyebabkan sejumlah masalah, kadang-kadang masalah serius seperti gangguan tenggat waktu untuk bekerja. Tapi setelah menghabiskan 2-3 hari setelah menginstal Arch, saya menemukan cara kerja OS saya. Setelah itu, ia mulai bekerja lebih stabil, tanpa kegagalan. Catatan saya membantu saya menginstalnya di PC baru dalam beberapa jam. Dokumentasi yang banyak membantu saya memecahkan masalah baru.
Kerangka kerja ini memiliki karakter tingkat yang sangat tinggi. Kelas-kelas yang menggambarkannya bertanggung jawab atas struktur aplikasi. Solusi pihak ketiga digunakan untuk berinteraksi dengan basis data, mengimplementasikan protokol http dan hal-hal tingkat rendah lainnya. Kami ingin programmer untuk melihat ke dalam kode ketika sebuah pertanyaan muncul dan memahami cara kerja kelas tertentu, dan dokumentasi akan memungkinkan kami untuk memahami cara mengelolanya. Memahami desain mesin tidak akan memungkinkan Anda mengendarai mobil.
Kerangka kerja
Sulit untuk menyebut LunaPark sebagai kerangka kerja dalam arti biasa. Bingkai - bingkai, Kerja - kerja. Kami mendesak untuk tidak membatasi diri pada ruang lingkup. Satu-satunya frame yang kami nyatakan adalah yang memberi tahu kelas di mana logika ini atau itu harus dijelaskan. Ini lebih merupakan seperangkat alat dengan instruksi luas untuk mereka.
Setiap kelas abstrak dan memiliki tiga tingkatan:
module LunaPark
Jika Anda ingin menerapkan formulir yang membuat elemen tunggal, Anda mewarisi dari kelas ini:
module Forms class Create < LunaPark::Forms::Single
Jika ada beberapa elemen, kami akan menggunakan Implementasi lain.
module Forms class Create < LunaPark::Forms::Multiple
Saat ini, tidak semua perkembangan telah dilakukan dengan sempurna dan permata dalam kondisi alfa. Kami akan mengutipnya secara bertahap, sesuai dengan publikasi artikel. Yaitu jika Anda melihat artikel tentang ValueObject
dan Entity
, maka kedua templat ini sudah diterapkan. Pada akhir siklus, semuanya akan cocok untuk digunakan pada proyek. Karena kerangka itu sendiri tidak banyak berguna tanpa tautan ke sinatra \ roda, repositori terpisah akan dibuat yang menunjukkan cara "mengacaukan semuanya" untuk memulai proyek Anda dengan cepat.
Kerangka kerja ini terutama merupakan aplikasi untuk dokumentasi. Jangan menganggap artikel-artikel ini sebagai dokumentasi untuk kerangka kerja.
Jadi, mari kita mulai bisnis.
Objek Nilai
- Seberapa tinggi pacar Anda?
- 151
- Anda mulai bertemu dengan patung kebebasan?
Sesuatu seperti ini bisa saja terjadi di Indiana. Pertumbuhan manusia bukan hanya angka, tetapi juga unit pengukuran. Tidak selalu atribut dari suatu objek hanya dapat dijelaskan oleh primitif (Integer, String, Boolean, dll.), Kadang-kadang diperlukan kombinasi dari mereka:
- Uang bukan hanya angka, itu angka (jumlah) + mata uang.
- Tanggal terdiri dari satu hari, satu bulan, dan satu tahun.
- Untuk mengukur berat, satu angka saja tidak cukup untuk kita, itu juga membutuhkan satuan pengukuran.
- Nomor paspor terdiri dari seri dan, pada kenyataannya, dari nomor tersebut.
Di sisi lain, ini tidak selalu merupakan kombinasi, mungkin itu adalah semacam perpanjangan dari primitif.
Nomor telepon sering diambil sebagai nomor. Di sisi lain, tidak mungkin ia memiliki metode penambahan atau pembagian. Mungkin ada metode yang akan mengeluarkan kode negara dan metode yang mendefinisikan kode kota. Mungkin akan ada metode dekoratif tertentu yang akan menyajikannya tidak hanya sebagai string angka 79001231212
, tetapi sebagai string yang dapat dibaca: 7-900-123-12-12
.
mungkin seorang dekorator?Berdasarkan dogma, tidak terbantahkan - ya. Jika kita mendekati dilema ini pada bagian dari akal sehat, maka ketika kita memutuskan untuk memanggil nomor ini, kita akan mentransfer objek itu sendiri ke telepon:
phone.call Values::PhoneNumber.new(79001231212)
Dan jika kami memutuskan untuk menyajikannya sebagai string, maka ini jelas dilakukan untuk seseorang. Jadi mengapa kita tidak langsung membuat baris ini dapat dibaca oleh seseorang?
Values::PhoneNumber.new(79001231212).to_s
Bayangkan kita menciptakan situs kasino online Three Axes dan menjual permainan kartu. Kita akan membutuhkan kelas 'kartu bermain'.
module Values class PlayingCard < Lunapark::Values::Compound attr_reader :suit, :rank end end
Jadi, kelas kami memiliki dua atribut read-only:
- suit - card suit
- martabat pangkat - kartu
Atribut ini hanya ditetapkan saat membuat peta dan tidak dapat berubah saat menggunakannya. Tentu saja Anda dapat mengambil kartu bermain dan mencoret 8 , tulis Q, tetapi ini tidak dapat diterima. Dalam masyarakat yang baik Anda kemungkinan besar akan ditembak. Ketidakmampuan untuk mengubah atribut setelah membuat objek menentukan properti pertama dari Value Object - immutability.
Properti penting kedua dari Object Value adalah bagaimana kita membandingkannya.
module Values RSpec.describe PlayingCard do let(:card) { described_class.new suit: :clubs, rank: 10 } let(:other) { described_class.new suit: :clubs, rank: 10 } it 'should be eql' do expect(card).to eq other end end end
Tes semacam itu akan gagal, karena akan dibandingkan di alamat. Agar tes dapat lulus, kita harus membandingkan Nilai-Obses dengan nilai, untuk ini kita akan menambahkan metode perbandingan:
def ==(other) suit == other.suit && rank == other.rank end
Sekarang ujian kami akan berlalu. Kami juga dapat menambahkan metode yang bertanggung jawab untuk perbandingan, tetapi bagaimana kami membandingkan 10 dan K? Seperti yang mungkin sudah Anda tebak, kami juga akan menyajikannya dalam bentuk Objek Nilai . Ok, jadi sekarang kita harus menginisiasi sepuluh klub top seperti ini:
ten = Values::Rank.new('10') clubs = Values::Suits.new(:clubs) ten_clubs = Values::PlayingCards.new(rank: ten, clubs: clubs)
Tiga baris sudah cukup untuk ruby. Untuk menghindari batasan ini, kami memperkenalkan properti ketiga dari Object-Value - turnover. Mari kita memiliki metode khusus kelas .wrap
, yang dapat mengambil nilai dari berbagai jenis dan mengubahnya menjadi yang benar.
class PlayingCard < Lunapark::Values::Compound def self.wrap(obj) case obj.is_a? self.class
Pendekatan ini memberi keuntungan besar:
ten = Values::Rank.new('10') clubs = Values::Suits.new(:clubs) from_values = Values::PlayingCard.wrap rank: ten, suit: clubs from_hash = Values::PlayingCard.wrap rank: '10', suit: :clubs from_obj = Values::PlayingCard.wrap from_values from_str = Values::PlayingCard.wrap '10C'
Semua kartu ini akan sama satu sama lain. Jika metode wrap
berkembang menjadi praktik yang baik, menempatkannya di kelas yang terpisah akan menjadi. Dari sudut pandang pendekatan dogmatis, kelas yang terpisah juga akan wajib.
Hmm, bagaimana dengan ruang di dek? Bagaimana cara mengetahui apakah kartu ini adalah kartu truf? Ini bukan kartu remi. Ini adalah nilai kartu bermain. Inilah tepatnya prasasti 10 yang Anda pimpin di sudut kardus.
Hal ini diperlukan untuk menghubungkan dengan Object-Value serta primitif, yang karena alasan tertentu tidak diterapkan dalam ruby. Dari sini, properti terakhir muncul - Nilai Objek tidak terikat ke domain mana pun.
Rekomendasi
Di antara seluruh variasi metode dan alat yang digunakan pada setiap momen dari setiap proses, selalu ada satu metode dan alat yang bekerja lebih cepat dan lebih baik daripada yang lain.
Frederick Taylor 1914
Operasi aritmatika harus mengembalikan objek baru
Atribut Obyek Nilai hanya dapat berupa primitif atau Objek Nilai lainnya
Simpan operasi sederhana di dalam metode kelas
Jika operasi "konversi" besar, maka mungkin masuk akal untuk memindahkannya ke kelas yang terpisah
Penghapusan logika seperti itu ke dalam Layanan terpisah hanya dimungkinkan dengan syarat Layanan terisolasi: tidak menggunakan data dari sumber eksternal apa pun. Layanan ini harus dibatasi oleh konteks Objek Nilai itu sendiri.
Nilai objek tidak dapat mengetahui apa pun tentang logika domain
Misalkan kita sedang menulis toko online, dan kita memiliki peringkat barang. Untuk mendapatkannya, Anda perlu membuat permintaan ke database melalui Repositori .
Entitas
Kelas Entity bertanggung jawab atas beberapa objek nyata. Itu bisa berupa kontrak, kursi, agen real estat, kue, besi, kucing, kulkas - apa pun. Objek apa pun yang Anda perlukan untuk memodelkan proses bisnis Anda adalah Entitas .
Konsep Entity berbeda untuk Evans dan Martin. Dari sudut pandang Evans, entitas adalah objek yang ditandai oleh sesuatu yang menekankan individualitasnya.
Esensi oleh ZvansJika suatu objek ditentukan oleh keberadaan individu yang unik, dan bukan oleh seperangkat atribut, properti ini harus dibaca sebagai yang utama ketika mendefinisikan objek dalam suatu model. Definisi kelas harus sederhana dan dibangun di sekitar kontinuitas dan keunikan siklus keberadaan objek. Temukan cara untuk membedakan setiap objek terlepas dari bentuk atau sejarah keberadaannya. Berikan perhatian khusus pada persyaratan teknis yang terkait dengan membandingkan objek sesuai dengan atributnya. Tentukan operasi yang akan memberikan hasil unik untuk setiap objek tersebut - mungkin perlu untuk mengaitkan simbol tertentu dengan keunikan yang dijamin untuk ini. Alat identifikasi semacam itu mungkin memiliki asal eksternal, tetapi juga dapat berupa pengidentifikasi sewenang-wenang yang dihasilkan oleh sistem untuk kenyamanannya sendiri. Namun, alat seperti itu harus mematuhi aturan untuk membedakan antara objek dalam model. Model harus memberikan definisi yang tepat tentang apa objek identik itu.
Dari sudut pandang Martin, Entity bukanlah objek, tetapi lapisan. Lapisan ini akan menggabungkan objek dan logika bisnis untuk mengubahnya.
Kesedihan dari MartinPandangan saya tentang Entitas adalah bahwa mereka mengandung aturan Bisnis Independen Aplikasi. Mereka bukan hanya objek data. Mereka mungkin memegang referensi ke objek data; tetapi tujuannya adalah untuk mengimplementasikan metode aturan bisnis yang dapat digunakan oleh banyak aplikasi yang berbeda.
Gateways mengembalikan Entitas. Implementasi (di bawah garis) mengambil data dari database, dan menggunakannya untuk membangun struktur data yang kemudian diteruskan ke Entitas. Ini dapat dilakukan baik dengan penahanan atau warisan.
Sebagai contoh:
MyEntity kelas publik {data pribadi MyDataStructure;}
atau
kelas publik MyEntity memperluas MyDataStructure {...}
Dan ingat, pada dasarnya kita semua adalah bajak laut; dan aturan yang saya bicarakan di sini benar-benar lebih seperti pedoman ...
Dengan Essence, yang kami maksud hanya struktur. Dalam bentuknya yang paling sederhana, kelas Entity akan terlihat seperti ini:
module Entities class MeatBag < LunaPark::Entities::Simple attr_accessor :id, :name, :hegiht, :weight, :birthday end end
Objek yang bisa berubah yang menggambarkan struktur model bisnis dapat berisi tipe dan Nilai primitif.
LunaPark::Entites::Simple
Kelas sederhana sangat sederhana, Anda dapat melihat kodenya, hanya memberi kita satu hal - inisialisasi mudah.
LunaPark :: Entites :: Sederhana module LunaPark module Entities class Simple def initialize(params) set_attributes params end private def set_attributes(hash) hash.each { |k, v| send(:"
Anda dapat menulis:
john_doe = Entity::MeatBag.new( id: 42, name: 'John Doe', height: '180cm', weight: '80kg', birthday: '01-01-1970' )
Seperti yang mungkin sudah Anda tebak, kami ingin membungkus berat, tinggi dan tanggal lahir di Object Nilai .
module Entities class MeatBag < LunaPark::Entites::Simple attr_accessor :id, :name attr_reader :heiht, :wight, :birthday def height=(height) @height = Values::Height.wrap(height) end def weight=(height) @height = Values::Weight.wrap(weight) end def birthday=(day) @birthday = Date.parse(day) end end end
Agar tidak membuang waktu pada konstruktor seperti itu, kami telah menyiapkan implementasi yang lebih kompleks dari LunaPark::Entites::Nested
:
module Entities class MeatBag < LunaPark::Entities::Nested attr :id attr :name attr :heiht, Values::Height, :wrap attr :weight, Values::Weight, :wrap attr :birthday, Values::Date, :parse end end
Seperti namanya, Implementasi ini memungkinkan Anda membuat struktur pohon.
Mari memuaskan hasrat saya untuk peralatan rumah tangga yang besar. Dalam artikel sebelumnya, kami menggambar analogi antara "twist" dari mesin cuci dan arsitektur . Dan sekarang kita akan menggambarkan objek bisnis yang penting seperti kulkas:

class Refregerator < LunaPark::Entites::Nested attr :id, attr :brand attr :title namespace :fridge do namespace :door do attr :upper, Shelf, :wrap attr :lower, Shelf, :wrap end attr :upper, Shelf, :wrap attr :lower, Shelf, :wrap end namespace :main do namespace :door do attr :first, Shelf, :wrap attr :second, Shelf, :wrap attr :third, Shelf, :wrap end namespace :boxes do attr :left, Box, :wrap attr :right, Box, :wrap end attr :first, Shelf, :wrap attr :second, Shelf, :wrap attr :third, Shelf, :wrap attr :fourth, Shelf, :wrap end attr :last_open_at, comparable: false end
Pendekatan ini menyelamatkan kita dari menciptakan Entitas yang tidak perlu, seperti pintu kulkas. Tanpa kulkas, itu harus menjadi bagian dari kulkas. Pendekatan ini nyaman untuk menyusun dokumen yang relatif besar, misalnya, aplikasi untuk pembelian asuransi.
The LunaPark::Entites::Nested
Kelas LunaPark::Entites::Nested
memiliki 2 properti lebih penting:
Keterbandingan:
module Entites class User < LunaPark::Entites::Nested attr :email attr :registred_at end end u1 = Entites::User.new(email: 'john.doe@mail.com', registred_at: Time.now) u2 = Entites::User.new(email: 'john.doe@mail.com', registred_at: Time.now) u1 == u2
Dua pengguna yang ditentukan tidak setara, karena mereka dibuat pada waktu yang berbeda dan karenanya nilai atribut registred_at
akan berbeda. Tetapi jika kita mencoret atribut dari daftar yang dibandingkan:
module Entites class User < LunaPark::Entites::Nested attr :email attr :registred_at, comparable: false end end
lalu kita mendapatkan dua objek yang sebanding.
Implementasi ini juga memiliki properti omset - kita dapat menggunakan metode kelas
Entites::User.wrap(email: 'john.doe@mail.com', registred_at: Time.now)
Anda dapat menggunakan Hash, OpenStruct atau permata apa pun yang Anda suka sebagai Entitas , yang akan membantu Anda mewujudkan struktur entitas Anda.
Entitas adalah model objek bisnis, biarkan itu tetap sederhana. Jika beberapa properti tidak digunakan oleh bisnis Anda, jangan jelaskan.
Perubahan Entitas
Seperti yang Anda perhatikan, kelas Entity tidak memiliki metode perubahannya sendiri. Semua perubahan dilakukan dari luar. Objek nilai juga tidak berubah. Semua fungsi yang ada di dalamnya, pada umumnya, menghiasi esensi atau membuat objek baru. Esensi itu sendiri tetap tidak berubah. Untuk pengembang Ruby on Rails, pendekatan ini tidak biasa. Dari luar sepertinya kita umumnya menggunakan bahasa OOP untuk sesuatu yang lain. Tetapi jika Anda melihat sedikit lebih dalam - ini tidak benar. Bisakah jendela terbuka sendiri? Dapatkan mobil untuk bekerja, pesan hotel, kucing lucu dapatkan pelanggan baru? Ini semua adalah pengaruh eksternal. Sesuatu terjadi di dunia nyata, dan kami merefleksikannya dalam diri kami. Untuk setiap permintaan, kami membuat perubahan pada model kami. Dan dengan demikian kami mempertahankannya tetap terbaru, cukup untuk tugas-tugas bisnis kami. Adalah perlu untuk memisahkan keadaan model dan proses yang menyebabkan perubahan dalam keadaan ini. Cara melakukan ini, akan kita bahas di artikel selanjutnya.