สถาปัตยกรรมสะอาดของลุงบ๊อบ - คลาสเอนทิตี้ / โมเดลสำหรับแต่ละเลเยอร์


44

พื้นหลัง :

ฉันกำลังพยายามใช้สถาปัตยกรรมที่สะอาดของลุงบ็อบในแอพ Android ของฉัน ฉันศึกษาโครงการโอเพนซอร์ซจำนวนมากที่พยายามแสดงวิธีการที่ถูกต้องและฉันพบว่ามีการนำไปใช้ที่น่าสนใจโดยใช้ RxAndroid

สิ่งที่ฉันสังเกตเห็น:

ในทุกเลเยอร์ (งานนำเสนอโดเมนและข้อมูล) มีคลาสโมเดลสำหรับเอนทิตีเดียวกัน (พูดถึง UML) นอกจากนี้ยังมีคลาส mapper ที่ดูแลการเปลี่ยนแปลงของวัตถุเมื่อใดก็ตามที่ข้อมูลข้ามเขตแดน (จากชั้นหนึ่งไปอีกชั้นหนึ่ง)

คำถาม:

มันจำเป็นต้องมีคลาสของโมเดลในทุกเลเยอร์หรือไม่เมื่อฉันรู้ว่าพวกมันทั้งหมดจะจบลงด้วยคุณลักษณะเดียวกันหากจำเป็นต้องใช้การดำเนินการ CRUD ทั้งหมดหรือไม่ หรือว่าเป็นกฎหรือแนวปฏิบัติที่ดีที่สุดเมื่อใช้สถาปัตยกรรมสะอาดหรือไม่

คำตอบ:


52

ในความคิดของฉันมันไม่ได้มีความหมายอย่างแท้จริง และเป็นการละเมิด DRY

แนวคิดคือวัตถุเอนทิตี / โดเมนที่อยู่ตรงกลางถูกสร้างแบบจำลองเพื่อแสดงโดเมนที่ดีและสะดวกที่สุด มันอยู่ในศูนย์กลางของทุกสิ่งและทุกอย่างขึ้นอยู่กับมันเนื่องจากโดเมนจะไม่เปลี่ยนแปลงส่วนใหญ่

หากฐานข้อมูลของคุณอยู่ด้านนอกสามารถจัดเก็บวัตถุเหล่านั้นได้โดยตรงการแมปไปยังรูปแบบอื่นเพื่อการแยกเลเยอร์นั้นไม่เพียง แต่ไร้ประโยชน์เท่านั้น แต่ยังเป็นการสร้างความซ้ำซ้อนของแบบจำลอง

ในการเริ่มต้นสถาปัตยกรรมสะอาดถูกสร้างขึ้นโดยคำนึงถึงสภาพแวดล้อม / สถานการณ์โดยทั่วไปที่แตกต่างกัน แอปพลิเคชันเซิร์ฟเวอร์ธุรกิจที่มีเลเยอร์นอกสุดที่ต้องการวัตถุพิเศษประเภทต่าง ๆ ตัวอย่างเช่นฐานข้อมูลที่ผลิตSQLRowวัตถุและต้องการSQLTransactionsกลับไปปรับปรุงรายการ หากคุณจะใช้สิ่งเหล่านี้ในศูนย์คุณจะต้องฝ่าฝืนทิศทางการพึ่งพาเพราะแกนกลางของคุณจะขึ้นอยู่กับฐานข้อมูล

ด้วยน้ำหนักเบา ORMs ที่โหลดและเก็บวัตถุเอนทิตีที่ไม่ใช่กรณี พวกเขาทำแผนที่ระหว่างภายในSQLRowและโดเมนของคุณ แม้ว่าคุณจะต้องใส่@Entitiyคำอธิบายประกอบของออมลงในวัตถุโดเมนของคุณฉันจะยืนยันว่าสิ่งนี้ไม่ได้สร้าง "กล่าวถึง" ของชั้นนอก เนื่องจากคำอธิบายประกอบเป็นเพียงข้อมูลเมตาไม่มีรหัสที่ไม่ได้ค้นหาโดยเฉพาะพวกเขาจะเห็นพวกเขา และที่สำคัญไม่มีความจำเป็นต้องเปลี่ยนแปลงหากคุณลบออกหรือแทนที่ด้วยคำอธิบายประกอบของฐานข้อมูลอื่น

ในทางตรงกันข้ามถ้าคุณเปลี่ยนโดเมนของคุณและคุณทำสิ่งที่แมปเหล่านั้นทั้งหมดคุณต้องเปลี่ยนมาก


คำแปรญัตติ: ข้างต้นเป็นสิ่งที่เล็กน้อยเกินไปและอาจผิด เนื่องจากมีส่วนหนึ่งในสถาปัตยกรรมแบบคลีนที่ต้องการให้คุณสร้างการแสดงต่อเลเยอร์ แต่นั่นจะต้องเห็นในบริบทของแอปพลิเคชัน

นี่คือที่นี่https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html

สิ่งสำคัญคือโครงสร้างข้อมูลที่แยกได้ง่ายถูกส่งผ่านข้ามขอบเขต เราไม่ต้องการโกงและส่งผ่านเอนทิตีหรือแถวฐานข้อมูล เราไม่ต้องการให้โครงสร้างข้อมูลมีการพึ่งพาใด ๆ ที่ละเมิดกฎการพึ่งพา

การส่งเอนทิตีจากจุดศูนย์กลางไปยังเลเยอร์ด้านนอกไม่ได้ละเมิดกฎการพึ่งพา แต่ก็ถูกกล่าวถึง แต่นี่เป็นเหตุผลในบริบทของแอพพลิเคชั่นที่จินตนาการ การส่งเอนทิตีไปรอบ ๆ จะย้ายแอปพลิเคชันตรรกะไปด้านนอก เลเยอร์ด้านนอกจำเป็นต้องรู้วิธีตีความวัตถุภายในพวกมันจะต้องทำในสิ่งที่เลเยอร์ภายในเช่น "กรณีใช้งาน" เลเยอร์ควรจะทำ

นอกจากนั้นมันยังแยกชั้นเพื่อให้การเปลี่ยนแปลงในแกนไม่จำเป็นต้องมีการเปลี่ยนแปลงในชั้นนอก (ดูความคิดเห็นของ SteveCallender) ในบริบทนั้นมันง่ายที่จะเห็นว่าวัตถุควรแสดงถึงวัตถุประสงค์ที่พวกเขาใช้ เลเยอร์นั้นควรพูดคุยกันในแง่ของวัตถุที่ทำขึ้นเป็นพิเศษเพื่อจุดประสงค์ในการสื่อสารนี้ นี่อาจหมายถึงว่ามี 3 การแทน 1 ในแต่ละเลเยอร์ 1 สำหรับการขนส่งระหว่างเลเยอร์

และมีhttps://blog.8thlight.com/uncle-bob/2011/11/22/Clean-Ar Architecture.htmlซึ่งมีที่อยู่ด้านบน:

คนอื่น ๆ กังวลว่าผลลัพธ์สุทธิของคำแนะนำของฉันจะเป็นรหัสซ้ำจำนวนมากและการคัดลอกข้อมูลจำนวนมากจากโครงสร้างข้อมูลหนึ่งไปยังอีกโครงสร้างหนึ่งทั่วทั้งชั้นของระบบ แน่นอนว่าฉันไม่ต้องการสิ่งนี้เช่นกัน และไม่มีสิ่งใดที่ฉันแนะนำจะนำไปสู่การซ้ำซ้อนของโครงสร้างข้อมูลและการคัดลอกฟิลด์ที่ไม่สมบูรณ์

IMO นั้นมีความหมายว่าการคัดลอกวัตถุ 1: 1 เป็นกลิ่นในสถาปัตยกรรมเพราะคุณไม่ได้ใช้เลเยอร์และ / หรือนามธรรมที่เหมาะสม

เขาอธิบายในภายหลังว่าเขาจินตนาการทั้งหมด "คัดลอก"

คุณแยก UI ออกจากกฎทางธุรกิจโดยการส่งผ่านโครงสร้างข้อมูลอย่างง่ายระหว่างทั้งสอง คุณไม่ให้ผู้ควบคุมของคุณรู้อะไรเกี่ยวกับกฎเกณฑ์ทางธุรกิจ แต่ผู้ควบคุมจะแกะวัตถุ HttpRequest ลงในโครงสร้างข้อมูลวานิลลาอย่างง่ายจากนั้นส่งโครงสร้างข้อมูลนั้นไปยังวัตถุที่มีปฏิสัมพันธ์ซึ่งใช้กรณีการใช้งานโดยเรียกใช้วัตถุทางธุรกิจ จากนั้นผู้โต้ตอบจะรวบรวมข้อมูลการตอบกลับไปยังโครงสร้างข้อมูลวานิลลาอื่นและส่งกลับไปยัง UI มุมมองไม่ทราบเกี่ยวกับวัตถุธุรกิจ พวกเขาแค่ดูในโครงสร้างข้อมูลนั้นและนำเสนอการตอบสนอง

ในแอปพลิเคชั่นนี้มีความแตกต่างอย่างมากระหว่างการเป็นตัวแทน ข้อมูลที่ไหลไม่ได้เป็นเพียงเอนทิตี และสิ่งนี้รับประกันและต้องการชั้นเรียนที่แตกต่างกัน

อย่างไรก็ตามนำไปใช้กับแอปพลิเคชัน Android อย่างง่ายเช่นโปรแกรมดูรูปภาพที่Photoมีกฎทางธุรกิจเกี่ยวกับ 0 และ "กรณีการใช้งาน" ที่เกี่ยวข้องกับพวกเขาเกือบจะไม่มีอยู่จริงและมีความกังวลมากขึ้นเกี่ยวกับการแคชและดาวน์โหลด แทนอย่างชัดเจนมากขึ้น) จุดที่จะทำให้การแสดงรูปภาพแยกกันหายไป ฉันยังรู้สึกว่ารูปถ่ายนั้นเป็นวัตถุการถ่ายโอนข้อมูลในขณะที่ชั้นตรรกะทางธุรกิจหลักที่แท้จริงหายไป

มีความแตกต่างระหว่างการเป็น"แยก UI จากกฎเกณฑ์ทางธุรกิจโดยผ่านโครงสร้างข้อมูลง่ายระหว่างสอง"และ"เมื่อคุณต้องการแสดงรูปภาพเปลี่ยนชื่อเป็นครั้งที่ 3 ในทาง"

นอกจากนั้นจุดที่ฉันเห็นแอปพลิเคชันสาธิตเหล่านั้นล้มเหลวในการแสดงถึงสถาปัตยกรรมแบบคลีนคือพวกเขาเพิ่มความสำคัญอย่างมากในการแยกเลเยอร์เพื่อแยกเลเยอร์ แต่ซ่อนสิ่งที่แอปพลิเคชันทำได้อย่างมีประสิทธิภาพ ซึ่งตรงกันข้ามกับที่กล่าวไว้ในhttps://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Ar Architecture.html - กล่าวคือ

สถาปัตยกรรมของแอปพลิเคชันซอฟต์แวร์กรีดร้องเกี่ยวกับกรณีการใช้งานของแอปพลิเคชัน

ฉันไม่เห็นความสำคัญในการแยกเลเยอร์ในสถาปัตยกรรมที่สะอาด มันเกี่ยวกับทิศทางการพึ่งพาอาศัยกันและมุ่งเน้นไปที่การเป็นแกนหลักของแอปพลิเคชัน - เอนทิตีและกรณีการใช้งาน - ใน java ธรรมดา ๆ โดยไม่ต้องพึ่งพาภายนอก มันไม่ได้เกี่ยวกับการพึ่งพาแกนกลางนั้นมากนัก

ดังนั้นหากแอปพลิเคชันของคุณมีแกนหลักที่แสดงถึงกฎเกณฑ์ทางธุรกิจและกรณีการใช้งานและ / หรือคนอื่นทำงานในเลเยอร์ที่แตกต่างกันโปรดแยกพวกเขาออกตามวิธีที่ตั้งใจไว้ หากคุณเพียงแค่เขียนแอพที่เรียบง่ายด้วยตัวเองอย่าหักโหมจนเกินไป 2 ชั้นที่มีขอบเขตคล่องแคล่วอาจเกินพอ และเลเยอร์สามารถเพิ่มในภายหลังได้เช่นกัน


1
@RamiJemli ตามหลักแล้วเอนทิตีจะเหมือนกันในทุกแอปพลิเคชัน นั่นคือความแตกต่างระหว่าง "กฎเกณฑ์ทางธุรกิจทั่วทั้งองค์กร" และ "กฎเกณฑ์ทางธุรกิจของแอปพลิเคชัน" (บางครั้งธุรกิจกับตรรกะของแอปพลิเคชัน) หลักคือการแสดงนามธรรมของเอนทิตีของคุณที่เป็นแบบทั่วไปมากพอที่คุณสามารถใช้ได้ทุกที่ ลองนึกภาพธนาคารที่มีแอปพลิเคชั่นมากมายหนึ่งแห่งสำหรับการสนับสนุนลูกค้าหนึ่งแห่งที่ทำงานบนเครื่องเงินสดหนึ่งแห่งเป็นเว็บ UI สำหรับลูกค้าของตัวเอง สิ่งเหล่านั้นสามารถใช้เหมือนกันBankAccountแต่มีกฎเฉพาะแอปพลิเคชันที่คุณสามารถทำได้กับบัญชีนั้น

4
ฉันคิดว่าจุดสำคัญในสถาปัตยกรรม clean คือการใช้ layer adapter interface เพื่อแปลง (หรือตามที่คุณพูด map) ระหว่างการแสดงเลเยอร์ต่าง ๆ ของเอนทิตีคุณลดการพึ่งพาเอนทิตีดังกล่าว หากมีการเปลี่ยนแปลงในเลเยอร์ Usecase หรือ Entity (หวังว่าไม่น่าเป็นไปได้ แต่เนื่องจากความต้องการเปลี่ยนเลเยอร์เหล่านี้เป็น) แล้วผลกระทบของการเปลี่ยนแปลงจะมีอยู่ในเลเยอร์อะแดปเตอร์ หากคุณเลือกที่จะใช้การเป็นตัวแทนเดียวกันของเอนทิตีทั้งหมดผ่านสถาปัตยกรรมของคุณผลกระทบของการเปลี่ยนแปลงนี้จะยิ่งใหญ่กว่ามาก
SteveCallender

1
@RamiJemli มันดีที่จะใช้เฟรมเวิร์กที่ทำให้ชีวิตง่ายขึ้นประเด็นก็คือคุณควรระวังเมื่อสถาปัตยกรรมของคุณต้องพึ่งพามันและคุณเริ่มวางมันไว้ที่ศูนย์กลางของทุกสิ่ง นี่คือแม้กระทั่งบทความเกี่ยวกับ RxJava blog.8thlight.com/uncle-bob/2015/08/06/let-the-magic-die.html - ไม่ได้บอกว่าคุณไม่ควรใช้ มันเหมือน: ฉันเคยเห็นสิ่งนี้มันจะแตกต่างกันในหนึ่งปีและเมื่อใบสมัครของคุณยังคงอยู่คุณก็ติดอยู่กับมัน ทำรายละเอียดและทำสิ่งที่สำคัญที่สุดในจาวาแบบเก่าธรรมดาในขณะที่ใช้หลักการ SOLID แบบธรรมดา
zapl

1
@zapl คุณรู้สึกแบบเดียวกันกับเลเยอร์บริการเว็บหรือไม่? กล่าวอีกนัยหนึ่งคุณจะใส่@SerializedNameคำอธิบายประกอบของ Gson ลงในแบบจำลองโดเมนได้หรือไม่ หรือคุณจะสร้างวัตถุใหม่ที่รับผิดชอบการแมปการตอบสนองของเว็บกับโมเดลโดเมนหรือไม่
tir38

2
@ tir38 การแยกตัวเองไม่ได้ให้ประโยชน์มันเป็นค่าใช้จ่ายของการเปลี่ยนแปลงในอนาคตที่เกิดขึ้นกับมัน => ขึ้นอยู่กับแอปพลิเคชัน 1) คุณต้องเสียเวลาในการสร้างและบำรุงรักษาขั้นตอนเพิ่มเติมที่เปลี่ยนระหว่างการเป็นตัวแทนที่แตกต่างกัน เช่นการเพิ่มฟิลด์ในโดเมนและลืมที่จะเพิ่มในที่อื่นไม่เคยได้ยินมาก่อน ไม่สามารถเกิดขึ้นได้ด้วยวิธีการง่ายๆ 2) มีค่าใช้จ่ายในการเปลี่ยนไปใช้การตั้งค่าที่ซับซ้อนขึ้นในภายหลังในกรณีที่คุณจำเป็นต้องใช้ การเพิ่มเลเยอร์ไม่ใช่เรื่องง่ายดังนั้นในแอปพลิเคชันขนาดใหญ่จึงสามารถปรับเลเยอร์ได้มากขึ้นซึ่งไม่จำเป็นต้องใช้ทันที
zapl

7

คุณเข้าใจถูกต้องจริง และไม่มีการละเมิด DRY เพราะคุณยอมรับ SRP

ตัวอย่างเช่น: คุณมีวิธีการทางธุรกิจ createX (ชื่อ String) จากนั้นคุณอาจมีวิธีการสร้าง createX (ชื่อสตริง) ใน DAO-Layer ที่เรียกว่าภายในวิธีการทางธุรกิจ พวกเขาอาจมีลายเซ็นเดียวกันและอาจมีเพียงการมอบหมาย แต่พวกเขามีวัตถุประสงค์ที่แตกต่างกัน คุณยังสามารถสร้าง createX (ชื่อ String) ได้ที่ UseCase แม้แล้วมันจะไม่ซ้ำซ้อน สิ่งที่ฉันหมายถึงคือลายเซ็นเดียวกันไม่ได้หมายถึงความหมายเดียวกัน เลือกชื่ออื่นเพื่อให้มีความหมายชัดเจน การตั้งชื่อตัวเองจะไม่ส่งผลกระทบต่อ SRP เลย

UseCase รับผิดชอบเกี่ยวกับตรรกะเฉพาะแอปพลิเคชันวัตถุธุรกิจรับผิดชอบต่อตรรกะที่ไม่ขึ้นอยู่กับแอปพลิเคชันและ DAO รับผิดชอบการจัดเก็บ

เนื่องจากความหมายที่แตกต่างกันทุกชั้นอาจมีรูปแบบการเป็นตัวแทนและรูปแบบการสื่อสารของตนเอง บ่อยครั้งที่คุณเห็น "เอนทิตี" เป็น "ออบเจกต์ธุรกิจ" และบ่อยครั้งที่คุณไม่เห็นความจำเป็นในการแยกพวกเขาออก แต่ในโครงการ "ใหญ่" ความพยายามนั้นควรแยกชั้นอย่างเหมาะสม ยิ่งโครงการมีความเป็นไปได้มากขึ้นคุณต้องมีความหมายต่างกันในเลเยอร์และคลาสที่ต่างกัน

คุณสามารถคิดถึงแง่มุมต่าง ๆ ของความหมายเดียวกัน ผู้ใช้ - วัตถุจะต้องปรากฏบนหน้าจอมันมีกฎความมั่นคงภายในและจะต้องเก็บไว้ที่ไหนสักแห่ง แต่ละด้านควรแสดงในคลาสอื่น (SRP) การสร้างผู้ทำแผนที่อาจเจ็บปวดในตูดดังนั้นในโครงการส่วนใหญ่ที่ฉันทำงานในด้านนี้จะถูกหลอมรวมเป็นหนึ่งชั้น เห็นได้ชัดว่านี่เป็นการละเมิด SRP แต่ไม่มีใครใส่ใจจริงๆ

ฉันเรียกแอปพลิเคชันของสถาปัตยกรรมแบบสะอาดและแบบโซลิด "ไม่เป็นที่ยอมรับทางสังคม" ฉันจะทำงานด้วยถ้าได้รับอนุญาต ขณะนี้ฉันไม่ได้รับอนุญาตให้ทำ ฉันรอสักครู่ที่เราต้องคิดเกี่ยวกับการใช้ของแข็งอย่างจริงจัง


ฉันคิดว่าไม่มีวิธีใดในชั้นข้อมูลที่ควรมีลายเซ็นเหมือนกันกับวิธีใด ๆ ในชั้นโดเมน ในเลเยอร์โดเมนคุณใช้หลักการตั้งชื่อที่เกี่ยวข้องกับธุรกิจเช่นการสมัครใช้งานหรือการเข้าสู่ระบบและในชั้นข้อมูลคุณใช้บันทึก (ถ้ารูปแบบ DAO) หรือเพิ่ม (ถ้าพื้นที่เก็บข้อมูลเพราะรูปแบบนี้ใช้การเก็บรวบรวมเป็นคำอุปมา) ในที่สุดฉันไม่ได้พูดถึงเอนทิตี (Data) และโมเดล (โดเมน) ฉันกำลังเน้นความไร้ประโยชน์ของ UserModel และ Mapper (เลเยอร์การนำเสนอ) คุณสามารถเรียกคลาสผู้ใช้ของโดเมนภายในงานนำเสนอและสิ่งนี้จะไม่ละเมิดกฎการพึ่งพา
Rami Jemli

ฉันเห็นด้วยกับ Rami ผู้ทำแผนที่ไม่จำเป็นเพราะคุณสามารถทำแผนที่โดยตรงในการนำไปใช้กับผู้โต้ตอบ
Christopher Perry

5

ไม่คุณไม่จำเป็นต้องสร้างคลาสโมเดลในทุกเลเยอร์

Entity ( DATA_LAYER) - เป็นการแสดงเต็มหรือบางส่วนของวัตถุฐานข้อมูลDATA_LAYER

Mapper ( DOMAIN_LAYER) - จริง ๆ แล้วเป็นคลาสที่แปลงเอนทิตีเป็น ModelClass ซึ่งจะใช้ในDOMAIN_LAYER

ลองดู: https://github.com/lifedemons/photoviewer


1
แน่นอนว่าฉันไม่ได้ต่อต้านเอนทิตีในเลเยอร์ข้อมูล แต่ในตัวอย่างของคุณคลาส PhotoModel ในเลเยอร์การนำเสนอมีคุณสมบัติเดียวกันของคลาส Photo ในโดเมนเลเยอร์ ในทางเทคนิคมันเป็นคลาสเดียวกัน จำเป็นไหม?

ฉันคิดว่ามีบางอย่างที่ปิดในตัวอย่างของคุณเนื่องจากเลเยอร์โดเมนไม่ควรขึ้นอยู่กับเลเยอร์อื่น ๆ ในตัวอย่างของคุณตัวแม็พของคุณขึ้นอยู่กับเอนทิตีในชั้นข้อมูลของคุณซึ่ง IMO นั้นควรเป็นตรงกันข้าม
navid_gh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.