From cfc7cfc177f97722dc0521782fcd6817d4a4ee0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Sat, 14 Apr 2018 22:44:15 +0300 Subject: [PATCH] Completed initial of the Module Architecture Best Practices & Conventions document. --- docs/Best-Practices/Module-Architecture.md | 63 +++++++++++++++++---- docs/images/module-layers-and-packages.jpg | Bin 0 -> 26892 bytes 2 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 docs/images/module-layers-and-packages.jpg diff --git a/docs/Best-Practices/Module-Architecture.md b/docs/Best-Practices/Module-Architecture.md index 3e0b6ae93e..aee414e0a8 100644 --- a/docs/Best-Practices/Module-Architecture.md +++ b/docs/Best-Practices/Module-Architecture.md @@ -1,29 +1,63 @@ ## Module Architecture Best Practices & Conventions -### Introduction - -TODO - ### Solution Structure * **Do** create a separated Visual Studio solution for every module. * **Do** name the solution as *CompanyName.ModuleName* (for core ABP packages, it's Volo.Abp.ModuleName). * **Do** develop the module as layered, so it has several projects (packages) those are related to each other. - * Every package has its own module definition file and explicitly adds [DependsOn] attribute for the depended packages/modules. + * Every package has its own module definition file and explicitly declare dependencies for the depended packages/modules. + +### Layers & Packages + +The following diagram shows the packages of a well-layered module and dependencies of those packages between them: + +![module-layers-and-packages](../images/module-layers-and-packages.jpg) + +The ultimate goal is to allow an application to use the module in a flexible manner. Example applications: + +* **A)** For a **monolithic** application; + * Adds references to the **Web** and the **Application** packages. + * Adds a reference to one of the **EF Core** or **MongoDB** packages based on preference. + * The result; + * The application **can show UI** of the module. + * It hosts the **application** and **domain** layers in the **same process** (that's why it needs to have a reference to a database integration package). + * This application also **serves** the module's **HTTP API** (since it includes the HttpApi package through the Web package). +* **B)** For an application that just serves the module as a **microservice**; + * Adds a reference to **HttpApi** and **Application** packages. + * Adds a reference to one of the **EF Core** or the **MongoDB** packages based on preference. + * The result; + * The application **can not show UI** of the module since it does not have a reference to the Web package. + * It hosts the **application** and **domain** layers in the **same process** (that's why it needs to have a reference to a database integration package). + * This application **serves** the module's **HTTP API** (as the main goal of the application). +* **C)** For an application that shows the module **UI** but does not host the application and just uses it as a remote service (that is hosted by the application A or B); + * Adds a reference to the **Web** and the **HttpApi.Client** packages. + * This application also serves the module API (since it includes the HttpApi package through the Web package) + * The result; + * The application **can show UI** of the module. + * It does not host the application and domain layers of the module in the same process. Instead, uses it as a **remote service**. + * This application also **serves** the module's **HTTP API** (since it includes the HttpApi package through the Web package). +* **D)** For a **client** application (or microservice) that just uses the module as a remote service (that is hosted by the application A, B or C); + * Adds a reference to the **HttpApi.Client** package. + * The result; + * The application can use all the functionality of the module as a **remote client**. + * The application is just a client and **can not serve** the **HTTP API** of the module. + * The application is just a client and **can not show** the **UI** of the module. + +Next section describes the packages in more details. #### Domain Layer * **Do** divide the domain layer into two projects: - * **Domain shared** package, named as *CompanyName.ModuleName.Domain.Shared*, contains constants, enums and other types those can be safely shared with the all layers of the module. This package can also be shared to 3rd-party clients. It can not contain entities, repositories, domain services or any other business objects. - * **Domain** package, named as *CompanyName.ModuleName.Domain*, contains entities, repository interfaces, domain service interfaces and their implementations and other domain objects. + * **Domain shared** package, named as *CompanyName.ModuleName.Domain.Shared*, that contains constants, enums and other types those can be safely shared with the all layers of the module. This package can also be shared to 3rd-party clients. It can not contain entities, repositories, domain services or any other business objects. + * **Domain** package, named as *CompanyName.ModuleName.Domain*, that contains entities, repository interfaces, domain service interfaces and their implementations and other domain objects. * Domain package depends on the **domain share** package. #### Application Layer * **Do** divide the application layer into two projects: - * **Application contracts** package, named as *CompanyName.ModuleName.Application.Contracts*, contains application service interfaces and related data transfer objects. + * **Application contracts** package, named as *CompanyName.ModuleName.Application.Contracts*, that contains application service interfaces and related data transfer objects. * Application contract package depends on the **domain shared** package. - * **Application** package, named as *CompanyName.ModuleName.Application*, contains application service implementations. + * **Application** package, named as *CompanyName.ModuleName.Application*, that contains application service implementations. * Application package depends on the **domain** and the **application contracts** packages. #### Infrastructure Layer @@ -37,5 +71,12 @@ TODO * **Do** create an **HTTP API** package, named as *CompanyName.ModuleName.HttpApi*, to develop a REST style HTTP API for the module. * HTTP API package only depends on the **application contracts** package. It does not depend on the application package. - * ... -* Do create an **HTTP API Client** package, named as *CompanyName.ModuleName.HttpApi.Client*,... \ No newline at end of file + * **Do** create a Controller for each application service (generally by implementing their interfaces). These controllers uses the application service interfaces to delegate the actions. +* **Do** create an **HTTP API Client** package, named as *CompanyName.ModuleName.HttpApi.Client*, to provide client services for the HTTP API package. Those client services implement application interfaces as clients to a remote endpoint. + * HTTP API package only depends on the **application contracts** package. + * **Do** use dynamic HTTP C# client proxy feature of the ABP framework. + +#### Web Layer + +* Do create a **Web** package, named as *CompanyName.ModuleName.Web*, that contains pages, views, scripts, styles, images and other UI components. + * Web package only depends on the **HTTP API** module. \ No newline at end of file diff --git a/docs/images/module-layers-and-packages.jpg b/docs/images/module-layers-and-packages.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f71a91eb8dc40c3999ea27c702012bd130771944 GIT binary patch literal 26892 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 literal 0 HcmV?d00001