เลเยอร์ของโดเมน

เลเยอร์โดเมนคือเลเยอร์ที่ไม่บังคับที่อยู่ระหว่างเลเยอร์ UI และ ของ Google Analytics

วันที่ เมื่อรวมไว้แล้ว เลเยอร์โดเมนที่ไม่บังคับจะให้ทรัพยากร Dependency ไปยัง
    เลเยอร์ UI และขึ้นอยู่กับชั้นข้อมูลด้วย
รูปที่ 1 บทบาทของเลเยอร์โดเมนในสถาปัตยกรรมแอป

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

เลเยอร์โดเมนมีประโยชน์ดังต่อไปนี้

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

เพื่อให้คลาสเหล่านี้เรียบง่ายและใช้ทรัพยากรน้อย Use Case แต่ละรายการควรมี มีหน้าที่เดียว และไม่ควรมีการเปลี่ยนแปลง คุณควรจัดการข้อมูลที่เปลี่ยนแปลงได้ใน UI หรือชั้นข้อมูลแทน

รูปแบบการตั้งชื่อในคู่มือนี้

ในคู่มือนี้ กรณีการใช้งานจะตั้งชื่อตามการดำเนินการรายการเดียวที่บุคคลนั้นรับผิดชอบ สำหรับ ซึ่งมีรายละเอียดดังนี้

กริยา ณ ปัจจุบัน + คำนาม/อะไร (ไม่บังคับ) + UseCase

เช่น FormatDateUseCase, LogOutUserUseCase GetLatestNewsWithAuthorsUseCase หรือ MakeLoginRequestUseCase

การขึ้นต่อกัน

ในสถาปัตยกรรมแอปทั่วไป คลาส Use Case ที่เหมาะสมระหว่าง ViewModels จาก เลเยอร์ UI และที่เก็บจากชั้นข้อมูล ซึ่งหมายความว่าคลาส Use Case มักจะขึ้นอยู่กับคลาสที่เก็บ และแท็กจะสื่อสารกับเลเยอร์ UI เช่นเดียวกับที่เก็บ โดยใช้ Callback (สำหรับ Java) หรือ Coroutine (สำหรับ Kotlin) หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องนี้ โปรดดูที่ชั้นข้อมูล

เช่น ในแอป คุณอาจมีคลาสกรณีการใช้งานที่ดึงข้อมูลจาก ที่เก็บข้อมูลข่าวและที่เก็บผู้แต่ง แล้วรวมเข้าด้วยกัน:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository
) { /* ... */ }

เนื่องจากกรณีการใช้งานมีตรรกะที่ใช้ซ้ำได้ จึงนำไปใช้งานในรูปแบบอื่นๆ ได้ด้วย กรณี การมีกรณีการใช้งานหลายระดับในเลเยอร์โดเมนนั้นถือเป็นเรื่องปกติ สำหรับ เช่น Use Case ที่กำหนดไว้ในตัวอย่างด้านล่างสามารถใช้ประโยชน์จาก FormatDateUseCase Use Case หากหลายคลาสจากเลเยอร์ UI ต้องใช้เวลา เพื่อแสดงข้อความที่เหมาะสมบนหน้าจอ:

class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }
GetLatestNewsWithAuthorsUseCase ขึ้นอยู่กับคลาสที่เก็บจาก
    แต่ก็ยังขึ้นอยู่กับ FormatDataUseCase ซึ่งเป็นคลาส Use Case อีกแบบหนึ่งด้วย
    ที่อยู่ในเลเยอร์โดเมนด้วย
รูปที่ 2 ตัวอย่างกราฟทรัพยากร Dependency สำหรับ Use Case ที่ขึ้นอยู่กับรูปแบบอื่น กรณีการใช้งาน

กรณีการใช้งานการโทรใน Kotlin

ใน Kotlin คุณสามารถทำให้อินสแตนซ์คลาส Use Case เรียกใช้เป็นฟังก์ชันได้โดย การกำหนดฟังก์ชัน invoke() ด้วยแป้นกดร่วม operator โปรดดูข้อมูลต่อไปนี้ ตัวอย่าง:

class FormatDateUseCase(userRepository: UserRepository) {

    private val formatter = SimpleDateFormat(
        userRepository.getPreferredDateFormat(),
        userRepository.getPreferredLocale()
    )

    operator fun invoke(date: Date): String {
        return formatter.format(date)
    }
}

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

class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
    init {
        val today = Calendar.getInstance()
        val todaysDate = formatDateUseCase(today)
        /* ... */
    }
}

โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับโอเปอเรเตอร์ invoke() ที่ Kotlin เอกสาร

วงจร

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

ด้ายกำจัดขน

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

ตัวอย่างต่อไปนี้แสดงกรณีการใช้งานที่ทํางานในเบื้องหลัง ชุดข้อความ:

class MyUseCase(
    private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {

    suspend operator fun invoke(...) = withContext(defaultDispatcher) {
        // Long-running blocking operations happen on a background thread.
    }
}

งานทั่วไป

หัวข้อนี้จะอธิบายวิธีทำงานทั่วไปของเลเยอร์โดเมน

ตรรกะทางธุรกิจที่เรียบง่ายซึ่งนำมาใช้ใหม่ได้

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

ลองดูตัวอย่าง FormatDateUseCase ที่อธิบายไว้ก่อนหน้านี้ หากธุรกิจของคุณ เกี่ยวกับการเปลี่ยนแปลงการจัดรูปแบบวันที่ในอนาคต คุณเพียงต้อง เปลี่ยนแปลงโค้ดได้แบบรวมศูนย์ในที่เดียว

รวมที่เก็บ

ในแอปข่าวสาร คุณอาจมีNewsRepository และAuthorsRepositoryชั้นเรียน ที่จัดการปฏิบัติการด้านข้อมูลข่าวและผู้แต่งตามลำดับ ชั้นเรียน Article ที่ NewsRepository แสดงจะมีเฉพาะชื่อของผู้เขียนเท่านั้น แต่คุณต้องการ เพื่อแสดงข้อมูลเพิ่มเติมเกี่ยวกับผู้แต่งบนหน้าจอ ข้อมูลผู้เขียน จะรับค่าได้จาก AuthorsRepository

วันที่ GetLatestNewsWithAuthorsUseCase ขึ้นอยู่กับที่เก็บ 2 รายการที่แตกต่างกัน
    คลาสจากชั้นข้อมูล: NewsRepository และ AuthorsRepository
รูปที่ 3 กราฟการขึ้นต่อกันสำหรับกรณีการใช้งานที่รวมข้อมูลจาก ที่เก็บได้หลายแหล่ง

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

/**
 * This use case fetches the latest news and the associated author.
 */
class GetLatestNewsWithAuthorsUseCase(
  private val newsRepository: NewsRepository,
  private val authorsRepository: AuthorsRepository,
  private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
    suspend operator fun invoke(): List<ArticleWithAuthor> =
        withContext(defaultDispatcher) {
            val news = newsRepository.fetchLatestNews()
            val result: MutableList<ArticleWithAuthor> = mutableListOf()
            // This is not parallelized, the use case is linearly slow.
            for (article in news) {
                // The repository exposes suspend functions
                val author = authorsRepository.getAuthor(article.authorId)
                result.add(ArticleWithAuthor(article, author))
            }
            result
        }
}

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

ผู้บริโภคอื่นๆ

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

การจำกัดการเข้าถึงชั้นข้อมูล

ข้อควรคำนึงอีกประการหนึ่งเมื่อใช้เลเยอร์โดเมนคือ คุณควร ยังคงอนุญาตการเข้าถึงชั้นข้อมูลโดยตรงจากเลเยอร์ UI หรือบังคับ ผ่านทางเลเยอร์โดเมน

วันที่ เลเยอร์ UI ไม่สามารถเข้าถึงชั้นข้อมูลโดยตรงได้ เนื่องจากจะต้องผ่านเลเยอร์โดเมน
รูปที่ 4 กราฟการขึ้นต่อกันที่แสดงให้เห็นว่าเลเยอร์ UI ถูกปฏิเสธการเข้าถึง ชั้นข้อมูล

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

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

วิธีที่ดีคือเพิ่ม Use Case เฉพาะเมื่อจำเป็นเท่านั้น หากคุณพบว่า UI ของคุณ เข้าถึงข้อมูลผ่าน Use Case เพียงอย่างเดียวได้เกือบทั้งหมด สมควรที่จะเข้าถึงข้อมูลด้วยวิธีนี้เท่านั้น

ท้ายที่สุดแล้ว การตัดสินใจที่จะจำกัดการเข้าถึงชั้นข้อมูล ฐานของโค้ดเฉพาะ และกำหนดว่าคุณต้องการกฎที่เข้มงวดหรือยืดหยุ่นกว่า ของเรา

การทดสอบ

ใช้หลักเกณฑ์การทดสอบทั่วไปเมื่อทดสอบโดเมน สำหรับการทดสอบ UI อื่นๆ นักพัฒนาซอฟต์แวร์มักจะใช้ที่เก็บปลอม คุณควรใช้ที่เก็บปลอมเมื่อทดสอบเลเยอร์โดเมนด้วย

ตัวอย่าง

ตัวอย่าง Google ต่อไปนี้สาธิตการใช้เลเยอร์โดเมน ศึกษาคู่มือเพื่อดูคำแนะนำนี้ในสถานการณ์จริง