Buku Masakan Pengembang: Resep DDD (Bagian 5, Proses)

Pendahuluan


Dalam artikel sebelumnya, kami menjelaskan: ruang lingkup , fondasi metodologis , contoh arsitektur dan struktur . Dalam artikel ini, saya ingin memberi tahu cara menggambarkan proses, tentang prinsip-prinsip persyaratan pengumpulan, bagaimana persyaratan bisnis berbeda dari yang fungsional, bagaimana cara beralih dari persyaratan ke kode. Bicarakan tentang prinsip-prinsip menggunakan Case Use dan bagaimana mereka dapat membantu kami. Jelajahi contoh opsi implementasi untuk pola desain Interactor dan Layer Layanan.


seperti nenek moyangmu


Contoh-contoh yang diberikan dalam artikel diberikan menggunakan solusi LunaPark kami, ini akan membantu Anda dengan langkah-langkah pertama dalam pendekatan yang dijelaskan.


Pisahkan persyaratan fungsional dari persyaratan bisnis.


Berkali-kali, banyak ide bisnis yang tidak benar-benar berubah menjadi produk akhir yang diinginkan. Hal ini sering disebabkan oleh ketidakmampuan untuk memahami perbedaan antara persyaratan bisnis dan persyaratan fungsional, yang pada akhirnya menyebabkan pengumpulan persyaratan yang tidak memadai, dokumentasi yang tidak perlu, penundaan proyek dan kegagalan proyek besar.


Atau kadang-kadang kita dihadapkan pada situasi di mana, meskipun solusi akhir memenuhi kebutuhan pelanggan, tetapi entah bagaimana tujuan bisnis tidak tercapai.


Oleh karena itu, sangat penting untuk memisahkan persyaratan bisnis dari persyaratan fungsional hingga Anda mulai mendefinisikannya. Mari kita ambil contoh.


Misalkan kita sedang menulis aplikasi untuk perusahaan pengiriman pizza, dan kami memutuskan untuk membuat sistem pelacakan kurir. Persyaratan bisnis adalah sebagai berikut:


"Memperkenalkan sistem berbasis web dan sistem pelacakan karyawan berbasis karyawan yang menangkap kurir pada rute mereka dan meningkatkan efisiensi dengan memantau aktivitas kurir, ketidakhadiran mereka dari pekerjaan dan produktivitas tenaga kerja."


Di sini kita dapat membedakan sejumlah fitur karakteristik yang akan menunjukkan bahwa ini adalah persyaratan dari bisnis:


  • persyaratan bisnis selalu ditulis dari sudut pandang klien;
  • Ini adalah persyaratan luas, tingkat tinggi, tetapi masih berorientasi sebagian;
  • mereka bukan tujuan perusahaan, tetapi membantu perusahaan mencapai tujuan;
  • jawab pertanyaan " mengapa " dan " apa ". Apa yang ingin diterima perusahaan? Dan mengapa dia membutuhkannya.

Persyaratan fungsional adalah Tindakan yang harus dilakukan sistem untuk melaksanakan persyaratan bisnis. Dengan demikian, persyaratan fungsional terkait dengan solusi atau perangkat lunak yang dikembangkan. Kami merumuskan persyaratan fungsional untuk contoh di atas:


  • sistem harus menampilkan garis bujur dan garis lintang karyawan melalui GPS / GLONASS;
  • sistem harus menampilkan posisi karyawan di peta;
  • sistem harus memungkinkan manajer untuk mengirim pemberitahuan kepada bawahannya.

Kami menyoroti fitur-fitur berikut:


  • persyaratan fungsional selalu ditulis dari sudut pandang sistem;
  • mereka lebih spesifik dan terperinci;
  • itu berkat pemenuhan persyaratan fungsional yang dikembangkan solusi efektif yang memenuhi kebutuhan bisnis dan tujuan klien;
  • jawab pertanyaan " bagaimana ." Bagaimana sistem memecahkan persyaratan bisnis.

Beberapa kata harus dikatakan tentang persyaratan non-fungsional (juga dikenal sebagai "persyaratan kualitas") yang memberlakukan pembatasan pada desain atau implementasi (misalnya, persyaratan untuk kinerja, keamanan, ketersediaan, keandalan). Persyaratan seperti itu menjawab pertanyaan " apa yang harus" menjadi sistem.


Pengembangan adalah terjemahan persyaratan bisnis menjadi fungsional. Pemrograman terapan adalah implementasi persyaratan fungsional, dan sistem - non-fungsional.


Gunakan kasing


Menerapkan persyaratan fungsional seringkali merupakan yang paling kompleks dalam sistem komersial. Dalam arsitektur murni, persyaratan fungsional diimplementasikan melalui lapisan Use Case .


Tapi sebagai permulaan, saya ingin beralih ke sumbernya. Ivar Jacobson - penulis definisi Use Case , salah satu penulis UML, dan metodologi RUP, dalam artikelnya Use-Case 2.0 Hub Pengembangan Perangkat Lunak mengidentifikasi 6 prinsip untuk menggunakan kasus-kasus Penggunaan:


  1. membuatnya sederhana melalui mendongeng;
  2. punya rencana strategis, waspadai keseluruhan gambaran;
  3. fokus pada makna;
  4. berbaris sistem dalam lapisan;
  5. mengirimkan sistem langkah demi langkah;
  6. memenuhi kebutuhan tim.

Kami mempertimbangkan secara singkat masing-masing prinsip ini, mereka berguna bagi kami untuk pemahaman lebih lanjut. Di bawah ini adalah terjemahan gratis saya, dengan singkatan dan sisipan, saya sangat menyarankan Anda membiasakan diri dengan yang asli.


Kesederhanaan melalui bercerita


Narasi adalah bagian dari budaya kita; Ini adalah cara termudah dan paling efektif untuk mentransfer pengetahuan, informasi dari satu orang ke orang lain. Ini adalah cara terbaik untuk mengkomunikasikan apa yang harus dilakukan sistem dan membantu tim fokus pada tujuan bersama.


Kasus penggunaan mencerminkan tujuan sistem. Untuk memahami Use Case, kami memberi tahu, menceritakan kisah tertentu. Ceritanya menceritakan bagaimana mencapai tujuan dan bagaimana menyelesaikan masalah yang muncul di sepanjang jalan. Gunakan kasing, seperti buku cerita, menyediakan cara untuk mengidentifikasi dan meliput semua cerita yang berbeda tetapi terkait dengan cara yang sederhana dan komprehensif. Ini membuatnya mudah untuk mengumpulkan, mendistribusikan, dan memahami persyaratan sistem.


Prinsip ini berkorelasi dengan bahasa Ubiques dari pendekatan DDD.


Memahami keseluruhan gambar


Terlepas dari sistem mana yang Anda kembangkan, besar, kecil, perangkat lunak, perangkat keras atau bisnis, memahami gambaran besar sangat penting. Tanpa memahami sistem secara keseluruhan, Anda tidak dapat membuat keputusan yang tepat tentang apa yang akan dimasukkan ke dalam sistem, apa yang harus dikesampingkan, berapa biayanya, dan manfaat apa yang akan dihasilkannya.


Ivar Jacobson menyarankan menggunakan diagram use case , yang sangat nyaman untuk mengumpulkan persyaratan. Jika persyaratan dikompilasi dan dihapus, maka peta Konteks Eric Evans adalah pilihan terbaik. Seringkali, pendekatan Scrum ditafsirkan sehingga orang tidak menghabiskan waktu untuk rencana strategis, mengingat perencanaan, lebih dari dua minggu kemudian, peninggalan masa lalu. Propaganda Jeff Sutherland jatuh pada aliran air, dan orang-orang yang menyelesaikan kursus pelatihan dua minggu untuk Scrum Masters yang diizinkan untuk mengelola proyek melakukan pekerjaan mereka. Tetapi akal sehat mengakui pentingnya perencanaan strategis. Tidak perlu membuat rencana strategis yang terperinci, tetapi haruslah demikian.


Fokus pada nilai


Ketika mencoba memahami bagaimana sistem akan digunakan, selalu penting untuk fokus pada nilai yang akan diberikannya kepada penggunanya dan pihak-pihak lain yang berkepentingan. Nilai terbentuk hanya ketika sistem digunakan. Oleh karena itu, jauh lebih baik untuk fokus pada bagaimana sistem akan diterapkan daripada pada daftar panjang fitur atau kemampuan yang dapat ditawarkannya.


Use case menyediakan fokus ini, membantu Anda berkonsentrasi pada bagaimana sistem akan digunakan oleh pengguna tertentu untuk mencapai tujuannya. Use cases mencakup banyak cara untuk menggunakan sistem: yang berhasil mencapai tujuan mereka, dan yang memecahkan kesulitan yang muncul.


Selanjutnya, penulis memberikan skema yang luar biasa, yang harus diperhatikan:



Diagram menunjukkan use case, "Penarikan tunai di ATM". Cara termudah untuk mencapai tujuan dijelaskan dalam Arah Dasar (Aliran dasar). Kasus-kasus lain digambarkan sebagai aliran alternatif. Arahan ini membantu bercerita, menyusun sistem, dan membantu menulis tes.


Layering


Sebagian besar sistem membutuhkan banyak pekerjaan sebelum siap digunakan. Mereka memiliki banyak persyaratan, yang sebagian besar tergantung pada persyaratan lain, mereka harus diimplementasikan sebelum persyaratan dipenuhi dan dievaluasi.


Adalah kesalahan besar untuk membuat sistem seperti itu pada suatu waktu. Sistem harus dibangun dari potongan-potongan, yang masing-masing memiliki nilai yang jelas bagi pengguna.


Ide-ide ini beresonansi dengan pendekatan gesit dan dengan ide-ide Domain .


Peluncuran produk langkah demi langkah


Sebagian besar sistem perangkat lunak telah berkembang selama beberapa generasi. Mereka tidak diproduksi pada suatu waktu; mereka dibangun sebagai serangkaian rilis, yang masing-masing dibangun pada rilis sebelumnya. Bahkan rilis itu sendiri sering tidak keluar sekaligus, tetapi berkembang melalui serangkaian versi perantara. Setiap langkah menyediakan versi sistem yang jelas dan dapat digunakan. Ini adalah cara semua sistem harus dibuat.


Memenuhi kebutuhan tim


Sayangnya, tidak ada solusi universal untuk masalah pengembangan perangkat lunak; tim yang berbeda dan situasi yang berbeda memerlukan gaya dan tingkat detail yang berbeda pula. Apa pun metode dan teknik yang Anda pilih, Anda harus memastikan bahwa mereka cukup mudah beradaptasi untuk memenuhi kebutuhan tim saat ini.


Eric Evans dalam bukunya mendesak Anda untuk tidak menghabiskan banyak waktu menjelaskan semua proses melalui UML. Cukup menggunakan skema visual apa pun. Tim yang berbeda, proyek yang berbeda memerlukan tingkat detail yang berbeda, karena penulis UML sendiri membicarakan hal ini.


Implementasi


Dalam arsitektur murni, Robert Martin mendefinisikan kasus Penggunaan berikut:


Use case ini mengatur aliran data ke dan dari entitas, dan mengarahkan entitas tersebut untuk menggunakan Aturan Bisnis Kritis mereka untuk mencapai tujuan use case.

Mari kita coba menerjemahkan ide-ide ini ke dalam kode. Mari kita ingat skema dari prinsip ketiga menggunakan Kasus Penggunaan dan menganggapnya sebagai dasar. Pertimbangkan proses bisnis yang sangat rumit: "Memasak Pai Kubis".


Mari kita coba mendekomposisinya:


  • periksa ketersediaan produk;
  • ambil dari stok;
  • remas adonan;
  • biarkan adonan mengembang;
  • menyiapkan isian;
  • membuat kue;
  • membuat kue pai.

Kami menerapkan seluruh urutan ini melalui Interactor , dan setiap langkah akan diimplementasikan melalui fungsi atau Objek Fungsional pada Lapisan Layanan.


Sequence of Actions (Interactor)


selangkah demi selangkah


Saya sangat merekomendasikan memulai pengembangan proses bisnis yang kompleks dengan Sequence of Actions . Lebih tepatnya, tidak demikian, Anda harus menentukan Domain Domain tempat proses bisnis berada. Perjelas semua persyaratan bisnis. Identifikasi semua Entitas yang terlibat dalam proses. Dokumentasikan persyaratan dan definisi masing-masing Entitas dalam basis pengetahuan.


Lukis semuanya di atas kertas dalam beberapa langkah. Terkadang Anda membutuhkan diagram urutan. Penulisnya adalah orang yang sama yang menemukan Use Case - Ivar Jacobson. Diagram itu ditemukan olehnya ketika ia mengembangkan sistem pemeliharaan jaringan telepon untuk Erickson, berdasarkan pada rangkaian relai. Saya sangat suka diagram ini, dan istilah Sequence , menurut pendapat saya, lebih ekspresif daripada istilah Interactor . Namun mengingat prevalensi yang lebih besar dari yang terakhir, kami akan menggunakan istilah yang akrab - Interactor .


Sedikit petunjuk ketika Anda menggambarkan proses bisnis adalah bantuan yang baik untuk Anda, aturan utama manajemen dokumen dapat menjadi: "Sebagai hasil dari aktivitas bisnis apa pun, dokumen harus dibuat". Sebagai contoh, kami sedang mengembangkan sistem diskon. Memberikan diskon, kami, pada kenyataannya, dari sudut pandang bisnis, menyimpulkan kesepakatan antara perusahaan dan klien. Semua persyaratan harus dijabarkan dalam kontrak ini. Yaitu, di domain DiscountSystem, Anda akan memiliki Entites :: Contract. Jangan ikat diskon dengan klien, tetapi buat Kontrak Entitas , yang menjelaskan aturan untuk ketentuannya.


Mari kita kembali ke deskripsi proses bisnis kita, setelah menjadi transparan bagi semua orang yang terlibat dalam pengembangannya, dan semua pengetahuan Anda sudah diperbaiki. Saya sarankan Anda mulai menulis kode dengan Sequence of Actions .


Templat Desain Urutan bertanggung jawab untuk:


  • urutan tindakan ;
  • koordinasi data yang dikirimkan antar Tindakan ;
  • memproses kesalahan yang dilakukan oleh Tindakan selama eksekusi mereka;
  • pengembalian hasil dari serangkaian Tindakan yang dilakukan;
  • PENTING : tanggung jawab paling penting dari pola desain ini adalah penerapan logika bisnis.

Saya ingin memikirkan tanggung jawab terakhir lebih terinci jika kita memiliki semacam proses yang kompleks - kita harus menggambarkannya sedemikian rupa sehingga jelas apa yang terjadi tanpa masuk ke rincian teknis. Anda harus mendeskripsikannya secara eksplisit sebagaimana kemampuan pemrograman Anda memungkinkan Anda . Percayakan kelas ini kepada anggota tim Anda yang paling berpengalaman.


Mari kita kembali ke pie: mari kita coba menggambarkan proses persiapannya melalui Interactor .


Implementasi


Saya memberikan contoh implementasi dengan solusi LunaPark kami, yang kami sajikan dalam artikel sebelumnya.


module Kitchen module Sequences class CookingPieWithabbage < LunaPark::Interactors::Sequence TEMPERATURE = Values::Temperature.new(180, unit: :cel) def call! Services::CheckProductsAvailability.call list: ingredients dough = Services::BeatDough.call from: Repository::Products.get(beat_ingredients) filler = Services::MakeabbageFiller.call from: Repository::Products.get(filler_ingredients) pie = Services::MakePie.call dough, with: filler bake = Services::BakePie.new pie, temp: TEMPERATURE sleep 5.min until bake.call pie end private attr_accessor :beat_ingredients, :filler_ingredients attr_accessor :pie def ingredients_list beat_ingredients_list + filler_ingredients_list end end end end 

Seperti yang bisa kita lihat, call! menjelaskan seluruh logika bisnis dari proses memanggang kue. Dan lebih mudah digunakan untuk memahami logika aplikasi.


Selain itu, kita dapat dengan mudah menggambarkan proses memanggang kue ikan, menggantikan MakeabbageFiller dengan MakeFishFiller . Dengan demikian, kami sangat cepat mengubah proses bisnis, tanpa modifikasi kode yang signifikan. Dan juga, kita dapat meninggalkan kedua Urutan pada saat yang sama, menskalakan kasus bisnis.


Pengaturan


  • Metode call! adalah metode yang diperlukan, itu menggambarkan urutan Tindakan .
  • Setiap parameter inisialisasi dapat dijelaskan melalui setter atau attr_acessor :

 class Foo < LunaPark::Interactors::Sequence # ... private attr_accessor :bar end Foo.call(bar: 42) 

  • Sisa metode harus bersifat pribadi.

Contoh penggunaan


 beat_ingredients = [ Entity::Product.new :flour, 500, :gr, Entity::Product.new :oil, 50, :gr, Entity::Product.new :salt, 1, :spoon, Entity::Product.new :milk, 150, :ml, Entity::Product.new :egg, 1, :unit, Entity::Product.new :yeast, 1, :spoon ] filler_ingredients = [ Entity::Product.new :cabbage, 500, :gr, Entity::Product.new :salt, 1, :spoon, Entity::Product.new :pepper, 1, :spoon ] cooking = CookingPieWithabbage.call( beat_ingredients: beat_ingredients, filler_ingredients: filler_ingredients ) #   : cooking.success? # => true cooking.fail # => false cooking.fail_message # => '' cooking.data # => Entity::Pie #   : cooking.success? # => false cooking.fail # => true cooking.fail_message # => 'The pie burned out' cooking.data # => nil 

Proses diwakili melalui objek dan kami memiliki semua metode yang diperlukan untuk memanggilnya - apakah panggilan berhasil, apakah ada kesalahan terjadi selama panggilan, dan jika demikian, yang mana?


Menangani kesalahan


Jika kita sekarang mengingat prinsip ketiga dari aplikasi Case Use, kami memperhatikan fakta bahwa selain jalur Utama , kami juga memiliki arahan Alternatif . Ini adalah kesalahan yang harus kita tangani. Pertimbangkan sebuah contoh: kami tentu tidak ingin acara seperti itu, tetapi kami tidak bisa melakukan apa-apa, kenyataan pahitnya adalah bahwa pai terbakar secara berkala.


Interactor mencegat semua kesalahan yang diwarisi dari LunaPark::Errors::Processing class.


Bagaimana cara melacak kue? Untuk melakukan ini, tentukan kesalahan Burned dalam Tindakan BakePie .


 module Kitchen module Errors class Burned < LunaPark::Errors::Processing; end end end 

Dan selama memanggang, pastikan pai kami belum habis:


 module Kitchen module Services class BakePie < LunaPark::Callable def call # ... rescue Errors::Burned, 'The pie burned out' if pie.burned? # ... end end end end 

Dalam hal ini, jebakan kesalahan akan berfungsi, dan kami akan dapat mengatasinya di .
Kesalahan yang tidak diwarisi dari Processing dianggap sebagai kesalahan sistem dan akan dicegat di tingkat server. Kecuali ditentukan lain, pengguna akan menerima 500 ServerError.


Penggunaan praktik


1. Cobalah untuk menggambarkan semua panggilan dalam metode panggilan!


Anda tidak boleh menerapkan setiap Tindakan dalam metode terpisah, ini membuat kode lebih membengkak. Anda harus melihat seluruh kelas beberapa kali untuk memahami cara kerjanya. Merusak resep untuk membuat kue:


 module Service class CookingPieWithabbage < LunaPark::Interactors::Sequence def call! check_products_availability make_cabbage_filler make_pie bake end private def check_products_availability Services::CheckProductsAvailability.call list: ingredients end # ... end end 

Gunakan panggilan aksi langsung di kelas. Dari sudut pandang ruby, pendekatan ini mungkin tampak tidak biasa, sehingga terlihat lebih mudah dibaca:


 class DrivingStart < LunaPark::Interactors::Sequence def call! Service::CheckEngine.call Service::StartUpTheIgnition.call car, with: key Service::ChangeGear.call car.gear_box, to: :drive Service::StepOnTheGas.call car.pedals[:right] end end 

2. Jika memungkinkan, gunakan metode kelas panggilan


 # good - ,   ,  . #    . Sequence::RingingToPerson.call(params) # good -   ,      e, #    ,     , #    . ring = Sequence::RingingToPerson.new(person) unless ring.success? ring.call sleep 5.min end 

3. Jangan membuat objek Fungsional demi mengetik kode, lihat situasinya


 # bad -        ,  #     . module Services class BuildUser < LunaPark::Callable def initialize(first_name:, last_name:, phone:) @first_name = first_name @last_name = last_name @phone = phone end def call Entity::User.new( first_name: first_name, last_name: last_name, phone: phone ) end private attr_reader :first_name, :last_name, :phone end end module Sequences class RegisteringUser < LunaPark::Interactors::Sequence attr_accessor :first_name, :last_name, :phone def call! user = Service::BuildUser.call(first_name: first_name, last_name: last_name, phone: phone) end end end # good -     ,  . #        , #       . module Sequences class RegisteringUser < LunaPark::Interactors::Sequence attr_accessor :first_name, :last_name, :phone def call! user #... end private def user @user = Entity::User.new( first_name: first_name, last_name: last_name, phone: phone ) end end end 

Lapisan Layanan


lsd


Interactor, seperti yang kami katakan, menggambarkan logika bisnis di tingkat tertinggi. Lapisan layanan sudah mengungkapkan detail penerapan persyaratan fungsional. Jika kita berbicara tentang membuat pai, maka pada tingkat Interactor kita cukup mengatakan "uleni adonan", tanpa merinci tentang cara meremasnya. Proses pengulungan dijelaskan pada tingkat Layanan . Mari kita kembali ke sumber aslinya, buku biru besar :


Di domain yang diterapkan, ada operasi yang tidak dapat menemukan tempat alami di objek bertipe Entity atau Value Object. Mereka secara inheren bukan objek, tetapi aktivitas. Tetapi karena dasar dari paradigma pemodelan kami adalah pendekatan objek, kami akan mencoba mengubahnya menjadi objek.


Pada titik ini mudah untuk membuat kesalahan umum: meninggalkan upaya untuk menempatkan operasi pada objek yang sesuai untuknya, dan dengan demikian sampai pada pemrograman prosedural. Tetapi jika Anda secara paksa menempatkan operasi pada objek dengan definisi yang asing terhadapnya, ini akan membuat objek itu sendiri kehilangan kemurniannya, membuatnya lebih sulit untuk dipahami dan diperbaiki. Jika Anda menerapkan banyak operasi kompleks dalam objek sederhana, itu bisa berubah menjadi apa yang tidak dapat dipahami, apa yang Anda lakukan tidak jelas apa. Operasi tersebut sering melibatkan objek lain dari area subjek dan koordinasi di antara mereka dilakukan untuk melakukan tugas bersama. Tanggung jawab tambahan menciptakan rantai ketergantungan antar objek, menggabungkan konsep yang dapat dipertimbangkan secara independen.


Saat memilih lokasi untuk implementasi fungsional, selalu gunakan akal sehat. Tugas Anda adalah membuat model lebih ekspresif. Mari kita lihat sebuah contoh, "Kita harus memotong kayu":


 module Entities class Wood def chop # ... end end end 

Metode ini akan menjadi kesalahan. Kayu bakar tidak akan memotong sendiri, kita membutuhkan kapak:


 module Entities class Axe def chop(sacrifice) # ... end end end 

Jika kita menggunakan model bisnis yang disederhanakan, itu sudah cukup. Tetapi jika proses perlu dimodelkan secara lebih rinci, kita akan membutuhkan seseorang yang akan memotong kayu bakar ini, dan mungkin beberapa batang kayu yang akan digunakan sebagai dudukan untuk proses tersebut.


 module Entities class Human def chop_firewood(wood, axe, chock) # ... end end end 

Seperti yang mungkin sudah Anda tebak, ini bukan ide yang baik. Tidak semua dari kita terlibat dalam memotong kayu, ini bukan tugas langsung seseorang. Kita sering melihat betapa kelebihan model di Ruby on Rails, yang mengandung logika serupa: mendapatkan diskon, menambahkan barang ke keranjang, menarik uang ke saldo. Logika ini tidak berlaku untuk entitas, tetapi untuk proses di mana entitas ini terlibat.


 module Services class ChopFirewood # ... end end 

Setelah kami menemukan logika apa yang kami simpan di Layanan, kami akan mencoba menerapkan salah satunya. Paling sering, layanan diimplementasikan melalui metode atau objek fungsional.


Objek Fungsional


Objek fungsional memenuhi satu persyaratan fungsional. Dalam bentuknya yang paling primitif, objek fungsional memiliki satu metode publik tunggal - call .


 module Serivices class Sum def initialize(x, y) @x = x @y = y end def call x + y end def self.call(x,y) new(x,y).call end private attr_reader :x, :y end end 

Objek semacam itu memiliki beberapa keunggulan: mereka ringkas, sangat mudah untuk diuji. Ada kekurangannya, objek seperti itu bisa berubah menjadi sejumlah besar. Ada beberapa cara untuk mengelompokkan objek yang serupa; di bagian proyek kami, kami membaginya berdasarkan jenis:


  • Obyek layanan (Layanan) - suatu objek, menciptakan objek baru;
  • Command (Command) - mengubah objek saat ini;
  • Guardian (Guard) - mengembalikan kesalahan jika terjadi kesalahan.

Obyek Layanan


Dalam implementasi kami, Layanan - mengimplementasikan persyaratan fungsional dan selalu mengembalikan nilai.


 module KorovaMilkBar module Services class FindMilk < LunaPark::Callable GLASS_SIZE = Values::Unit.wrap '200g' def initialize(fridge:) @fridge = fridge end def call fridge.shelfs.find { |shelf| shelf.has?(GLASS_SIZE, of: :milk) } end private attr_reader :fridge end end end FindMilk.call(fridge: the_red_one) # => #<Glass: ... > 

Perintah


Dalam implementasi kami, Perintah - melakukan satu Tindakan , memodifikasi objek, jika benar mengembalikan benar. Sebenarnya, Tim tidak membuat objek, tetapi memodifikasi yang sudah ada.


 module KorovaMilkBar module Commands class FillGlass < LunaPark::Callable def initialize(glass, with:) @glass = glass @content = with end def call glass << content true end private attr_reader :fridge end end end glass = Glass.empty milk = Milk.new(200, :gr) glass.empty? # => true FillGlass.call glass, with: milk # => true glass.empty? # => false 

Wali (Penjaga)


Penjaga melakukan pemeriksaan logis dan jika gagal memberikan kesalahan pemrosesan. Jenis objek ini tidak mempengaruhi Arah Utama dengan cara apa pun, tetapi mengalihkan kita ke Arah Alternatif jika terjadi kesalahan.


Saat menyajikan susu, penting untuk memastikan susu segar:


 module KorovaMilkBar module Guards class IsFresh < LunaPark::Callable def initialize(product) @products = products end def call products.each do |product| raise Errors::Rotten, "#{product.title} is not fresh" if product.expiration_date > Date.today end nil end private attr_reader :products end end end 

Anda mungkin merasa nyaman untuk memisahkan objek fungsional berdasarkan jenis. Anda dapat menambahkan milik Anda sendiri, misalnya, Builder - membuat objek berdasarkan parameter.


Pengaturan


  • Metode call adalah satu-satunya metode publik wajib.
  • Metode initialize adalah satu-satunya metode publik opsional.
  • Sisa metode harus bersifat pribadi.
  • Kesalahan logis harus diwarisi dari kelas LunaPark::Errors::Processing .

Menangani kesalahan


Ada 2 jenis kesalahan yang dapat terjadi selama operasi suatu Tindakan .


Kesalahan Runtime

Kesalahan tersebut dapat terjadi sebagai akibat dari pelanggaran logika pemrosesan.


Sebagai contoh:


  • saat membuat email pengguna dicadangkan;
  • ketika Anda mencoba minum susu, itu sudah berakhir;
  • microservice lain menolak tindakan (karena alasan logis, dan bukan karena layanan tidak tersedia).

Kemungkinan besar, pengguna ingin tahu tentang kesalahan ini. Juga, ini mungkin kesalahannya
yang bisa kita ramalkan.


Kesalahan semacam itu harus diwarisi dari LunaPark::Errors::Processing


Kesalahan sistem

Kesalahan yang terjadi sebagai akibat dari sistem crash.


Sebagai contoh:


  • database tidak berfungsi;
  • sesuatu dibagi dengan nol.

Dalam semua kemungkinan, kami tidak dapat melihat kesalahan ini dan tidak bisa mengatakan apa pun kepada pengguna, kecuali bahwa semuanya sangat buruk, dan mengirimkan laporan kepada pengembang yang meminta tindakan. Kesalahan semacam itu harus diwarisi dari SystemError


Ada juga kesalahan validasi , yang akan kita diskusikan secara lebih rinci di artikel selanjutnya.


Penggunaan praktik


1. Gunakan variabel untuk meningkatkan keterbacaan


 module Fishing # bad -   Serivices::Catch.call(fish, rod) # bad -  Serivices::Catch.call(fish: fish, rod: rod) # good -   Serivices::Catch.call(fish, with: rod) module Serivices class Catch def initialize(fish, with:) @fish = fish @rod = with #      #   . end # ... private attr_reader :fish, :rod end end end 

2. Lulus objek, bukan parameter


Cobalah untuk membuat penginisialisasi sederhana jika pemrosesan parameter tidak tujuannya.
Lulus objek, bukan parameter.


 module Service # bad -        -.  #      ,   . class Foo def initialize(foo_params:, bar_params:) @foo = Values::Foo.new(*foo_params) @bar = Values::Bar.new(*bar_params) end # ... end Services::Foo.call(foo: {a: 1, b: 2}, bar: 34) # good -   -. class Bar def initialize(foo:, bar:) @foo = foo @bar = bar # ... end end foo = Values::Foo.new(a: 1, b: 2) bar = Values::Bar.new(34) Services::Bar.call(foo: foo, bar: bar) # good -       - Builder. class BuildFoo def initialize(param_1:, param_2:) @param_1 = param_1 @param_1 = param_1 end def call Foo.new( param_1: param_1.foo, param_2: param_2.bar, param_3: some_magick ) end # ... end end 

3. Gunakan nama Tindakan - kata kerja dari tindakan dan objek pengaruh.


 # bad module Services class Milk; end class Work; end class FooBuild; end class PasswordGenerator; end end # good module Services class GetMilk; end class WorkOnTable; end class BuildFoo; end class GeneratePassword; end end 

4. Jika memungkinkan, gunakan metode kelas panggilan


Biasanya instance dari kelas Actions , jarang digunakan selain menulis untuk membuat panggilan.


 # good -    . Services::BuildFoo.call(params) # good -     Services::BuildFoo.(params) # good -   ,      , #    ,     ,   #  . ring = Services::RingToPhone.new(phone: neighbour) 10.times do ring.call end 

5. Penanganan kesalahan bukan tugas layanan


 # bad -    ,   . def call #... rescue SystemError => e return false end 

Modul


Hingga saat ini, kami menganggap implementasi lapisan Layanan sebagai seperangkat objek fungsional. Tetapi kita dapat dengan mudah menempatkan metode pada layer ini:


 module Services def sum(a, b) a + b end end 

Masalah lain yang menghadang kita adalah sejumlah besar fasilitas layanan. Alih-alih "model lemak rel", yang kami dapatkan di tepi, kami mendapatkan "folder layanan lemak". Ada beberapa cara untuk mengatur struktur untuk mengurangi skala tragedi. Eric Evans memecahkan ini dengan menggabungkan sejumlah fungsi ke dalam satu kelas. Bayangkan kita perlu memodelkan proses bisnis pengasuh, Arina Rodionovna, dia bisa memberi makan Pushkin dan menidurkannya:


 class NoonService def initialize(arina_radionovna, pushkin) # ... end def to_feed # ... end def to_sleep # ... end end 

Pendekatan ini lebih tepat dari sudut pandang OOP. Namun kami menyarankan untuk mengabaikannya, setidaknya pada tahap awal. Pemrogram yang tidak terlalu berpengalaman mulai menulis banyak kode di kelas ini, yang pada akhirnya mengarah pada peningkatan konektivitas. Sebagai gantinya, Anda dapat menggunakan modul, mewakili aktivitas sebagai beberapa abstraksi:


 module Services module Noon class ToFeed def call! # ... end end class << self #    ,   #    def to_feed(arina_radionovna, pushkin) ToFeed.new(arina_radionovna, pushkin).call end #    ,    def to_sleep(arina_radionovna, pushkin) arina_radionovna.tell_story pushkin pushkin.state = :sleep end end end end 

Ketika membaginya menjadi beberapa modul, kopling eksternal rendah (kopling rendah) harus diamati dengan kohesi internal yang tinggi (kohesi tinggi), tetapi kami menggunakan modul seperti Layanan atau Interaktor, ini juga bertentangan dengan gagasan arsitektur murni. Ini adalah pilihan sadar yang memfasilitasi persepsi. Dengan nama file, kami memahami pola desain mana yang diterapkan kelas ini atau itu, jika bagi seorang programmer berpengalaman ini jelas, maka untuk seorang pemula hal ini tidak selalu terjadi. Setelah tim Anda siap, buang kelebihan ini.


Mengutip kutipan kecil lain dari buku biru besar:


Pilih modul yang memberi tahu sejarah sistem dan berisi serangkaian konsep yang koheren. Dari sinilah seringkali ketergantungan modul yang rendah satu sama lain muncul dengan sendirinya. Tetapi jika ini tidak terjadi, cari cara untuk mengubah model sedemikian rupa untuk memisahkan konsep satu sama lain, atau mencari konsep yang hilang dalam model, yang dapat menjadi dasar untuk modul dan dengan demikian menyatukan unsur-unsur model bersama-sama dengan cara alami, bermakna. Mencapai ketergantungan rendah modul satu sama lain dalam arti bahwa konsep dalam modul yang berbeda dapat dianalisis dan dirasakan secara independen satu sama lain. Perbaiki model sampai batas-batas alami muncul di dalamnya sesuai dengan konsep tingkat tinggi dari area subjek, dan kode yang sesuai tidak dibagi sesuai.


Berikan nama modul yang akan dimasukkan dalam BAHASA YANG TIDAK DIKENAL. Baik MODUL itu sendiri maupun namanya harus mencerminkan pengetahuan dan pemahaman tentang bidang subjek.


Topik modulnya besar dan menarik, tetapi secara lengkap melampaui topik artikel ini. Lain kali kami akan berbicara dengan Anda tentang Gudang dan Adaptor . Kami membuka saluran telegram yang nyaman tempat kami ingin berbagi materi tentang topik DDD. Kami menyambut pertanyaan dan umpan balik Anda.




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


All Articles