From 56387bca2b9215924e346a1d1b245cc9427e6485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 13:53:41 +0300 Subject: [PATCH 1/8] Improve the part 1. --- docs/en/tutorials/modular-crm/part-01.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-01.md b/docs/en/tutorials/modular-crm/part-01.md index 98698b481df..61698fb48ad 100644 --- a/docs/en/tutorials/modular-crm/part-01.md +++ b/docs/en/tutorials/modular-crm/part-01.md @@ -10,7 +10,7 @@ } ```` -Follow the [Get Stared](../../get-started/layered-web-application.md) guide to create a new layered web application with the following configuration: +Follow the *[Get Stared](../../get-started/layered-web-application.md)* guide to create a new layered web application with the following configuration: * **Solution name**: `ModularCrm` * **UI Framework**: ASP.NET Core MVC / Razor Pages @@ -20,7 +20,7 @@ You can select the other options based on your preference. > **Please complete the [Get Stared](../../get-started/layered-web-application.md) guide and run the web application before going further.** -The initial solution structure should be like the following in ABP Studio's *Solution Explorer*: +The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*: ![solution-explorer-modular-crm-initial](images/solution-explorer-modular-crm-initial.png) From 72bd20a986b813c9351c6b78dd7d0fed4889fa47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 14:02:15 +0300 Subject: [PATCH 2/8] Grammer fixes --- docs/en/tutorials/modular-crm/part-02.md | 26 ++++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-02.md b/docs/en/tutorials/modular-crm/part-02.md index edc6f6bb384..14560b37667 100644 --- a/docs/en/tutorials/modular-crm/part-02.md +++ b/docs/en/tutorials/modular-crm/part-02.md @@ -14,11 +14,11 @@ } ```` -In this part, you will build a new module for product management and install it to the main CRM application. +In this part, you will build a new product management module and install it in the main CRM application. ## Creating Solution Folders -You can create solution folders and sub-folders in *Solution Explorer* to better organize your solution components. Right-click to the solution root on the *Solution Explorer* panel, and select *Add* -> *New Folder* command: +You can create solution folders and sub-folders in *Solution Explorer* to organize your solution components better. Right-click to the solution root on the *Solution Explorer* panel, and select *Add* -> *New Folder* command: ![abp-studio-add-new-folder-command](images/abp-studio-add-new-folder-command.png) @@ -26,7 +26,7 @@ That command opens a dialog where you can set the folder name: ![abp-studio-new-folder-dialog](images/abp-studio-new-folder-dialog.png) -Create `main` and `modules` folder using the *New Folder* command, then move the `ModularCrm` module into the `main` folder (simply by drag & drop). The *Solution Explorer* panel should look like the following figure now: +Create a `main` and a `modules` folder using the *New Folder* command, then move the `ModularCrm` module into the `main` folder (simply by drag & drop). The *Solution Explorer* panel should look like the following figure now: ![abp-studio-solution-explorer-with-folders](images/abp-studio-solution-explorer-with-folders.png) @@ -35,15 +35,15 @@ Create `main` and `modules` folder using the *New Folder* command, then move the There are two module templates provided by ABP Studio: * **Empty Module**: You can use that module template to build your module structure from scratch. -* **DDD Module**: A Domain-Driven Design based layered module structure. +* **DDD Module**: A Domain Driven Design based layered module structure. -We will use the *DDD Module* template for the Product module. We will use the *Empty Module* template later in this tutorial. +We will use the *DDD Module* template for the Product module and the *Empty Module* template later in this tutorial. Right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *DDD Module* command: ![abp-studio-add-new-dd-module](images/abp-studio-add-new-dd-module.png) -This command opens a new dialog to define properties of the new module. You can use the following values to create a new module named `ModularCrm.Products`: +This command opens a new dialog to define the properties of the new module. You can use the following values to create a new module named `ModularCrm.Products`: ![abp-studio-create-new-module-dialog](images/abp-studio-create-new-module-dialog.png) @@ -58,7 +58,7 @@ Here, you can select the UI type you want to support in your module: A module; * May not contain a UI and leaves the UI development to the final application. -* May contain a single UI implementation that is typically in the same technology with the main application. +* May contain a single UI implementation that is typically in the same technology as the main application. * May contain more than one UI implementation if you want to create a reusable application module and you want to make that module usable by different applications with different UI technologies. For example, all of [pre-built ABP modules](https://abp.io/modules) support multiple UI options. In this tutorial, we are selecting the MVC UI since we are building that module only for our `ModularCrm` solution and we are using the MVC UI in our application. So, select the MVC UI and click the *Next* button. @@ -93,11 +93,11 @@ As seen in the preceding figure, the `ModularCrm.Products` solution consists of ### Installing the Product Module to the Main Application -A module does not contain an executable application inside. The `Modular.Products.Web` project is just a class library project, not an executable web application. A module should be installed to an executable application in order to run it. +A module does not contain an executable application inside. The `Modular.Products.Web` project is just a class library project, not an executable web application. A module should be installed in an executable application to run it. > **Ensure that the web application is not running in [Solution Runner](../../studio/running-applications.md) or in your IDE. Installing a module to a running application will produce errors.** -The product module has no relation to the main application yet. Right-click to the `ModularCrm` module (inside the `main` folder) and select the *Import Module* command: +The product module has yet to be related to the main application. Right-click on the `ModularCrm` module (inside the `main` folder) and select the *Import Module* command: ![abp-studio-import-module](images/abp-studio-import-module.png) @@ -105,7 +105,7 @@ The *Import Module* command opens a dialog as shown below: ![abp-studio-import-module-dialog](images/abp-studio-import-module-dialog.png) -Select the `ModularCrm.Products` module and check the *Install this module* option. If you don't check that option, it only imports the module but doesn't setup project dependencies. Importing a module without installation can be used to manually setup your project dependencies. Here, we want to make it automatically, so checking the *Install this module* option. +Select the `ModularCrm.Products` module and check the *Install this module* option. If you don't check that option, it only imports the module but doesn't set project dependencies. Importing a module without installation can be used to set up your project dependencies manually. We want to make it automatically, so check the *Install this module* option. When you click the *OK* button, ABP Studio opens the *Install Module* dialog: @@ -117,13 +117,13 @@ The default package match is good for this tutorial, so you can click the *OK* b ### Building the Main Application -After the installation, build the entire solution by right-clicking to the `ModularCrm` module (under the `main` folder) and select the *Dotnet CLI* -> *Graph Build* command: +After the installation, build the entire solution by right-clicking on the `ModularCrm` module (under the `main` folder) and selecting the *Dotnet CLI* -> *Graph Build* command: ![abp-studio-graph-build](images/abp-studio-graph-build.png) -Graph Build is a dotnet CLI command that recursively build all the referenced dotnet projects even if they are not a part of the root solution. +Graph Build is a dotnet CLI command that recursively builds all the referenced dotnet projects, even if they are not part of the root solution. -> While developing multi-module solutions with ABP Studio, you may need to perform *Graph Build* on the root/main module if you made changes in the depending modules. +> While developing multi-module solutions with ABP Studio, you may need to perform *Graph Build* on the root/main module if you change the depending modules. ### Run the Main Application From d58ad799da8fdbd901f2b33a423de4e110c2243e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 14:13:05 +0300 Subject: [PATCH 3/8] Grammer fixes in part 3 --- docs/en/tutorials/modular-crm/part-03.md | 74 ++++++++++++------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-03.md b/docs/en/tutorials/modular-crm/part-03.md index c3230e8c025..799745b9c84 100644 --- a/docs/en/tutorials/modular-crm/part-03.md +++ b/docs/en/tutorials/modular-crm/part-03.md @@ -14,7 +14,7 @@ } ```` -In this part, you will learn how to create entities, services and a basic user interface for the products module. +In this part, you will learn how to create entities and services and a basic user interface for the products module. > **This module's functionality will be minimal to focus on modularity.** You can follow the [Book Store tutorial](../book-store/index.md) to learn building more real-world applications with ABP. @@ -47,7 +47,7 @@ public class Product : AggregateRoot ## Mapping Entity to Database -The next step is to configure Entity Framework Core `DbContext` class and the database for the new entity. +The next step is to configure the Entity Framework Core `DbContext` class and the database for the new entity. ### Add a `DbSet` Property @@ -101,11 +101,11 @@ public interface IProductsDbContext : IEfCoreDbContext } ```` -Having such an `IProductsDbContext` interface allows us to decouple our repositories (and other classes) from the concrete `ProductsDbContext` class, which provides flexibility to the final application to merge multiple `DbContext`s into a single `DbContext` to manage database migrations easier and have a database level transaction support for multi-module database operations. +Having such an `IProductsDbContext` interface allows us to decouple our repositories (and other classes) from the concrete `ProductsDbContext` class. This provides flexibility to the final application to merge multiple `DbContext`s into a single `DbContext` to manage database migrations easier and have a database level transaction support for multi-module database operations. ### Configure the Table Mapping -The DDD module template has designed flexible so that your module can have a separate physical database, or store its tables inside another database, like the main database of your application. To make that possible, it configures the database mapping in an extension method (`ConfigureProducts`) that is called inside the `OnModelCreating` method above. Find that extension method (in the `ProductsDbContextModelCreatingExtensions` class) and change its content as the following code block: +The DDD module template is designed to be flexible so that your module can have a separate physical database or store its tables inside another database, like the main database of your application. To make that possible, it configures the database mapping in an extension method (`ConfigureProducts`) called inside the `OnModelCreating` method above. Find that extension method (in the `ProductsDbContextModelCreatingExtensions` class) and change its content as the following code block: ````csharp using Microsoft.EntityFrameworkCore; @@ -137,7 +137,7 @@ public static class ProductsDbContextModelCreatingExtensions } ```` -As first, we are setting the database table name with the `ToTable` method. `ProductsDbProperties.DbTablePrefix` defines a constant that is added as a prefix to all database table names of this module. If you see the `ProductsDbProperties` class (in the `ModularCrm.Products.Domain` project), `DbTablePrefix` value is `Products`. In that case, the table name for the `Product` entity will be `ProductsProducts`. We think that is unnecessary for such a simple module and we can remove that prefix. So, you can change the `ProductsDbProperties` class with the following content to set an empty string to the `DbTablePrefix` property: +First, we are setting the database table name with the `ToTable` method. `ProductsDbProperties.DbTablePrefix` defines a constant that is added as a prefix to all database table names of this module. If you see the `ProductsDbProperties` class (in the `ModularCrm.Products.Domain` project), `DbTablePrefix` value is `Products`. In that case, the table name for the `Product` entity will be `ProductsProducts`. That is unnecessary for such a simple module; we can remove that prefix. So, you can change the `ProductsDbProperties` class with the following content to set an empty string to the `DbTablePrefix` property: ````csharp namespace ModularCrm.Products; @@ -150,15 +150,15 @@ public static class ProductsDbProperties } ```` -You can set a `DbSchema` to collect a module's tables under a separate schema (if it is supported by your DBMS), or use a `DbTablePrefix` as a prefix for all tables names of a module. For this tutorial, we won't set any of them. +You can set a `DbSchema` to collect a module's tables under a separate schema (if your DBMS supports it) or use a `DbTablePrefix` as a prefix for all module table names. We won't set any of them for this tutorial. -In that point, build the `ModularCrm.Products` .NET solution in your IDE (or in ABP Studio UI). We will switch to the main application's .NET solution. +At that point, build the `ModularCrm.Products` .NET solution in your IDE (or ABP Studio UI). We will switch to the main application's .NET solution. ### Configuring the Main Application Database -We changed the Entity Framework Core configuration. The next step should be adding a new code-first database migration and update the database, so the new Products table is created on the database. +We changed the Entity Framework Core configuration. The next step should be adding a new code-first database migration and updating the database so the new Products table is created on the database. -We are not managing the database migrations in the module. Instead, the main application decides which DBMS (Database Management System) to use and how to share physical database(s) among modules. To keep this tutorial simple, we will store all the data of all modules in a single physical database. +We are not managing the database migrations in the module. Instead, the main application decides which DBMS (Database Management System) to use and how to share physical database(s) among modules. We will store all the modules' data in a single physical database to simplify this tutorial. Open the `ModularCrm` module (which is the main application) in your IDE: @@ -172,7 +172,7 @@ We will merge module's database configuration into `ModularCrmDbContext`. #### Replace the `IProductsDbContext` Service -Follow the 3 steps below; +Follow the three steps below; **(1)** Add the following attribute on top of the `ModularCrmDbContext` class: @@ -206,7 +206,7 @@ protected override void OnModelCreating(ModelBuilder builder) } ```` -In this way, `ModularCrmDbContext` can be used by the products module over the `IProductsDbContext` interface. This part is only needed for one time for a module. Next time, you can just add a new database migration as explained in the next section. +In this way, `ModularCrmDbContext` can be used by the products module over the `IProductsDbContext` interface. This part is only needed once for a module. Next time, you can add a new database migration, as explained in the next section. #### Add a Database Migration @@ -222,7 +222,7 @@ The *Add Migration* command opens a new dialog to get a migration name: ![abp-studio-add-entity-framework-core-migration-dialog](images/abp-studio-add-entity-framework-core-migration-dialog.png) -Once you click the *OK* button, a new database migration class is added into the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: ![visual-studio-new-migration-class](images/visual-studio-new-migration-class.png) @@ -256,11 +256,11 @@ public interface IProductAppService : IApplicationService } ```` -We are defining application service interfaces and [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md) in the `Application.Contracts` project. In that way, we can share that contracts with clients without sharing the actual implementation class. +We are defining application service interfaces and [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md) in the `Application.Contracts` project. That way, we can share those contracts with clients without sharing the actual implementation class. ### Defining Data Transfer Objects -`GetListAsync` and `CreateAsync` methods are using `ProductDto` and `ProductCreationDto` classes that are not defined yet. So, we need to define them. +The `GetListAsync` and `CreateAsync` methods use the `ProductDto` and `ProductCreationDto` classes, which have not been defined yet. So, we need to define them. Create a `ProductCreationDto` class under the `ModularCrm.Products.Application.Contracts` project: @@ -340,11 +340,11 @@ public class ProductAppService : ProductsAppService, IProductAppService } ```` -Notice that `ProductAppService` class implements the `IProductAppService` and also inherits from the `ProductsAppService` class. Do not confuse about the naming (`ProductAppService` and `ProductsAppService`). The `ProductsAppService` is a base class. It makes a few configuration for [localization](../../framework/fundamentals/localization.md) and [object mapping](../../framework/infrastructure/object-to-object-mapping.md) (you can see in the `ModularCrm.Products.Application` project). You can inherit all of your application services from that base class. In this way, you can define some common properties and methods to share among all your application services. You can rename the base class if you feel that you may confuse later. +Notice that `ProductAppService` class implements the `IProductAppService` and also inherits from the `ProductsAppService` class. Do not be confused about the naming (`ProductAppService` and `ProductsAppService`). The `ProductsAppService` is a base class. It makes a few configurations for [localization](../../framework/fundamentals/localization.md) and [object mapping](../../framework/infrastructure/object-to-object-mapping.md) (you can see in the `ModularCrm.Products.Application` project). You can inherit all of your application services from that base class. This way, you can define some common properties and methods to share among all your application services. You can rename the base class if you feel that you may be confused later. #### Object Mapping -`ProductAppService.GetListAsync` method uses the `ObjectMapper` service to convert `Product` entities to `ProductDto` objects. The mapping should be configured. Open the `ProductsApplicationAutoMapperProfile` class in the `ModularCrm.Products.Application` project and change it as the following code block: +`ProductAppService.GetListAsync` method uses the `ObjectMapper` service to convert `Product` entities to `ProductDto` objects. The mapping should be configured. Open the `ProductsApplicationAutoMapperProfile` class in the `ModularCrm.Products.Application` project and change it to the following code block: ````csharp using AutoMapper; @@ -360,14 +360,14 @@ public class ProductsApplicationAutoMapperProfile : Profile } ```` -We've just added the `CreateMap();` line to define the mapping. +We've added the `CreateMap();` line to define the mapping. ### Exposing Application Services as HTTP API Controllers -For this application, we don't need to create HTTP API endpoints for the products module actually. But, it is good to understand how to do it when you need. You have two options; +For this application, we don't need to create HTTP API endpoints for the products module. But it is good to understand how to do it when you need it. You have two options; -* You can create a regular ASP.NET Core Controller class into the `ModularCrm.Products.HttpApi` project, inject `IProductAppService` and use it to create wrapper methods. We will do it later while we will create the Ordering module. -* Alternatively, you can just use the ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature to automatically expose your application services as API controllers by conventions. We will do it here. +* You can create a regular ASP.NET Core Controller class in the `ModularCrm.Products.HttpApi` project, inject `IProductAppService` and use it to create wrapper methods. We will do this later while we create the Ordering module. +* Alternatively, you can use the ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature to expose your application services as API controllers by conventions. We will do it here. Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method: @@ -378,7 +378,7 @@ PreConfigure(mvcBuilder => }); ```` -This will tell to ASP.NET Core to explore the given assembly to discover controllers. +This will tell the ASP.NET Core to explore the given assembly to discover controllers. Then open the `ConfigureAutoApiControllers` method of the same class and add a second `ConventionalControllers.Create` call as shown in the following code block: @@ -392,21 +392,21 @@ Configure(options => }); ```` -This will tell to ABP framework to create API controllers for the application services in the given assembly. +This will tell the ABP framework to create API controllers for the application services in the assembly. -> We made these configurations in the main application's solution since there is no project in the product module's solution that has reference to ASP.NET Core MVC packages and uses the application layer of the product module. If you add a reference of `ModularCrm.Products.Application` to `ModularCrm.Products.HttpApi`, then you can move these configurations to the `ModularCrm.Products.HttpApi` project. +> We made these configurations in the main application's solution since there is no project in the product module's solution that references ASP.NET Core MVC packages and uses the product module's application layer. If you add a reference of `ModularCrm.Products.Application` to `ModularCrm.Products.HttpApi`, then you can move these configurations to the `ModularCrm.Products.HttpApi` project. -Now, the application services defined in the `ModularCrm.Products.Application` project will be exposed as API controllers automatically by ABP. In the next section, we will use these API controller to create some example products. +Now, ABP will automatically expose the application services defined in the `ModularCrm.Products.Application` project as API controllers. The next section will use these API controllers to create some example products. ### Creating Example Products -In this section, we will create a few example products using the [Swagger UI](../../framework/api-development/swagger.md). In that way, we will have some sample products to show on the UI. +This section will create a few example products using the [Swagger UI](../../framework/api-development/swagger.md). Thus, we will have some sample products to show on the UI. -Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel, select the *Dotnet CLI* -> *Graph Build* command. In this way, we can be sure that the product module and the main application has built and ready to run. +Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the product module and the main application are built and ready to run. -After the build process completes, open the Solution Runner panel and click the *Play* button near to the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface. +After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface. -Once you see the user interface of the web application, type `/swagger` to the end of the URL to open the Swagger UI. If you scroll down, you should see the `Products` API: +Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Products` API: ![abp-studio-swagger-ui-in-browser](images/abp-studio-swagger-ui-in-browser.png) @@ -414,7 +414,7 @@ Expand the `/api/app/product` API and click the *Try it out* button as shown in ![abp-studio-swagger-ui-create-product-try](images/abp-studio-swagger-ui-create-product-try.png) -Then create a few products by filling the *Request body* and clicking the *Execute* button: +Then, create a few products by filling in the *Request body* and clicking the *Execute* button: ![abp-studio-swagger-ui-create-product-execute](images/abp-studio-swagger-ui-create-product-execute.png) @@ -422,15 +422,15 @@ If you check the database, you should see the entities created in the `Products` ![sql-server-products-database-table-filled](images/sql-server-products-database-table-filled.png) -We've some entities in the database, and we can show them on the user interface now. +We've some entities in the database; we can show them on the user interface now. ## Creating the User Interface -In this section, we will create a very simple user interface to demonstrate how to build UI in the products module and make it working in the main application. +In this section, we will create a very simple user interface to demonstrate how to build UI in the products module and make it work in the main application. -As a first step, stop the application on ABP Studio's Solution Runner if it is currently running. +As a first step, you can stop the application on ABP Studio's Solution Runner if it is currently running. -Open the `ModularCrm.Products` .NET solution in your IDE, find the `Pages/Products/Index.cshtml` file under the `ModularCrm.Products.Web` project: +Open the `ModularCrm.Products` .NET solution in your IDE, and find the `Pages/Products/Index.cshtml` file under the `ModularCrm.Products.Web` project: ![visual-studio-products-cshtml](images/visual-studio-products-cshtml.png) @@ -460,7 +460,7 @@ public class IndexModel : ProductsPageModel } ```` -Here, we are simply using the `IProductAppService` to get a list of all products and assigning the result to the `Products` property. We can use it in the `Index.cshtml` file to show a simple list of products on the UI: +Here, we simply use the `IProductAppService` to get a list of all products and assign the result to the `Products` property. We can use it in the `Index.cshtml` file to show a simple list of products on the UI: ````xml @page @@ -494,13 +494,13 @@ Now, you can browse the *Products* page to see the list of the products: ![abp-studio-browser-list-of-products](images/abp-studio-browser-list-of-products.png) -As you see, it is pretty straightforward to develop a UI page in a modular ABP application. We kept the UI very simple to focus on the modularity. If you want to learn how to build complex application UIs, please check the [Book Store Tutorial](../book-store/index.md). +As you can see, developing a UI page in a modular ABP application is pretty straightforward. We kept the UI very simple to focus on modularity. To learn how to build complex application UIs, please check the [Book Store Tutorial](../book-store/index.md). ## Final Notes Some of the projects in the product module's .NET solution (`ModularCrm.Products`) are not necessary for most of the cases. They are available to support different scenarios. You can delete them from your module (and remove the dependencies on the main application) if you want: -* `ModularCrm.Products.HttpApi`: That project is to define regular HTTP API controllers. If you will always use ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature (like we did in this tutorial), you can delete that project. +* `ModularCrm.Products.HttpApi`: This project aims to define regular HTTP API controllers. If you will always use ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature (like we did in this tutorial), you can delete that project. * `ModularCrm.Products.HttpApi.Client`: That project is generally shared with 3rd-party applications, so they can easily consume your HTTP API endpoints. In a modular monolith application, you typically don't need it. -* `ModularCrm.Products.HttpApi.Installer`: That project is used to discover and install a multi-projects module (like the product module) when you deploy it to a package management system (like NuGet). If you will use the module with local project references (like we did here), you can delete that project. +* `ModularCrm.Products.HttpApi.Installer`: That project is used to discover and install a multi-projects module (like the product module) when you deploy it to a package management system (like NuGet). If you use the module with local project references (like we did here), you can delete that project. * You can also delete the test projects (there are 4 of them in the solution) if you don't prefer to write unit/integration tests in the module's solution (Legal warning: it is recommended to write tests 😊) From 5af90590ce77fbe443eef90ea68f6af31e313f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 14:39:52 +0300 Subject: [PATCH 4/8] Grammer fixes for part 04 --- docs/en/tutorials/modular-crm/part-04.md | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-04.md b/docs/en/tutorials/modular-crm/part-04.md index a8938fd6188..ea7e66cc8b1 100644 --- a/docs/en/tutorials/modular-crm/part-04.md +++ b/docs/en/tutorials/modular-crm/part-04.md @@ -14,11 +14,11 @@ } ```` -In this part, you will build a new module for placing orders and install it to the main CRM application. +In this part, you will build a new module for placing orders and install it in the main CRM application. ## Creating an Empty Module -We have used the *DDD Module* template for the Product module. We will use the *Empty Module* template for the Ordering module in this part. +In this part, we have used the *DDD Module* template for the Product module and will use the *Empty Module* template for the Ordering module. Right-click the `modules` folder on the *Solution Explorer* panel, and select the *Add* -> *New Module* -> *Empty Module* command: @@ -38,18 +38,18 @@ Since we've created an empty module, it is really empty. If you open the `module ## Creating the Module Packages -In this section, we will create packages under the Ordering module. The Products module was well layered based on Domain Driven Design principles. This time, we will keep the things very simple and will only create two packaged for the Ordering module: +In this section, we will create packages under the Ordering module. The Products module was well layered based on Domain Driven Design principles. This time, we will keep things very simple and will only create two packages for the Ordering module: -* `ModularCrm.Ordering`: Contains all the module code without any layering. It will contains entities, database access code, services, controllers, UI pages and whatever we need to implement the *Ordering* module. +* `ModularCrm.Ordering`: Contains all the module code without any layering. It will contain entities, database access code, services, controllers, UI pages and whatever we need to implement the *Ordering* module. * `ModularCrm.Ordering.Contracts`: Contains the services and objects we want to share with other modules. `ModularCrm.Ordering` uses (and implements) this package. -> If your modules are relatively small, easy to maintain, they will only be used by your main application and you don't care about layering, you can create such simple module structures. +> If your modules are relatively small and easy to maintain, they will only be used by your main application, and you don't care about layering, then you can create such simple module structures. -We will create and configure everything from scratch. You have already learned the easy way in the previous parts where we've created the Products module. It is time to have a deep understanding of the details and learn how ABP Studio allows you to setup custom structures. Let's begin... +We will create and configure everything from scratch. You have already learned the easy way in the previous parts, where we created the Products module. It is time to deepen your understanding of the details and learn how ABP Studio allows you to set up custom structures. Let's begin. ### Creating the `ModularCrm.Ordering.Contracts` Package -We will start by creating the `ModularCrm.Ordering.Contracts` package. Right-click the `ModularCrm.Ordering` module on the *Solution Explorer* and select the *Add* -> *Package* -> *New Package* command as shown in the following figure: +Right-click the `ModularCrm.Ordering` module on the *Solution Explorer* and select the *Add* -> *Package* -> *New Package* command as shown in the following figure: ![abp-studio-add-new-package](images/abp-studio-add-new-package.png) @@ -57,7 +57,7 @@ That command opens a new dialog to create a new package: ![abp-studio-add-new-package-class-library](images/abp-studio-add-new-package-class-library.png) -With that dialog, you can build your module or application layer by layer. There are templates for any type of package. However, here we will go with the most simple one: *ABP Class Library*. ABP Class Library is an empty C# class library with the [core ABP package](https://www.nuget.org/packages/Volo.Abp.Core) dependency and a [module class](../../framework/architecture/modularity/basics.md). +With that dialog, you can build your module or application layer by layer. There are templates for any package. However, here we will go with the simplest one: *ABP Class Library*. ABP Class Library is an empty C# class library with the [core ABP package](https://www.nuget.org/packages/Volo.Abp.Core) dependency and a [module class](../../framework/architecture/modularity/basics.md). Type `ModularCrm.Ordering.Contracts` as the *Package name* and click the *Create* button. It will add the package under the `ModularCrm.Ordering` module: @@ -69,7 +69,7 @@ Right-click the `ModularCrm.Ordering` module on the *Solution Explorer* again an ![abp-studio-add-new-package-mvc-ui](images/abp-studio-add-new-package-mvc-ui.png) -This time, we are selecting the *MVC UI* template and set the *Package name* as `ModularCrm.Ordering`. Click the *Create* button to add the new package. +This time, we select the MVC UI template and set the Package name to `ModularCrm.Ordering`. Then, click the Create button to add the new package. ### Add Package Reference @@ -81,17 +81,17 @@ That command opens a dialog to select the package: ![abp-studio-add-package-reference-dialog](images/abp-studio-add-package-reference-dialog.png) -In that dialog, you can add package reference from various sources. Here, we will add reference for the package in the current module. So, select the `ModularCrm.Ordering.Contracts` package in *This module* tab and click the *OK* button. You can see the package reference under the *Projects* dependencies on the *Solution Explorer* panel: +In that dialog, you can add package references from various sources. Here, we will add a reference for the package in the current module. So, select the `ModularCrm.Ordering.Contracts` package in the *This module* tab and click the *OK* button. You can see the package reference under the *Projects* dependencies on the *Solution Explorer* panel: ![abp-studio-project-reference-example](images/abp-studio-project-reference-example.png) -The initial module creation has been completed. Now, we have a new module with 2 packages. However, it has no relation to other modules and applications in the solution yet. +The initial module creation has been completed. Now, we have a new module with two packages. However, it is not related to other modules and applications in the solution yet. ## Installing into the Main Application -In this section, we will install the `ModularCrm.Ordering` module to the main application, so it can be a part of the system. +In this section, we will install the `ModularCrm.Ordering` module in the main application so it can be part of the system. -> Before the installation, ensure that the web application is not running. +> Before the installation, please ensure the web application is not running. Right-click the `ModularCrm` module (under the `main` folder) and select the *Import Module* command: @@ -105,8 +105,8 @@ Select the `ModularCrm.Ordering` module and check the *Install this module* opti ![abp-studio-install-module-dialog](images/abp-studio-install-module-dialog.png) -Select the `ModuleCrm.Ordering` package on the left area, `ModularCrm.Domain` package on the middle area as shown in the preceding figure and click the *OK* button. +Select the `ModuleCrm.Ordering` package in the left area and the `ModularCrm.Domain` package in the middle area, as shown in the preceding figure, and click the *OK* button. -> Since the Ordering module is not layered, we didn't install its packages to the layers of our main application. We are installing it only to `ModularCrm.Domain`. In this way, we can use the Ordering module from any layer of our application since `ModularCrm.Domain` is one of the core packages of our application. If you build your modules as non-layered and you don't have much code in the main application's .NET solution, you can also consider to create a non-layered main application that composes these modules. +> Since the Ordering module is not layered, we didn't install its packages to the layers of our main application. We are installing it only to `ModularCrm.Domain`. In this way, we can use the Ordering module from any layer of our application since `ModularCrm.Domain` is one of the core packages of our application. If you build your modules as non-layered and you don't have much code in the main application's .NET solution, you can also consider creating a non-layered main application that composes these modules. -In this part of the tutorial, we've created an empty module and added packages. In that way, you can create modules or applications with custom structure. In the next part, we will add functionality to the Ordering module. \ No newline at end of file +In this part of the tutorial, we've created an empty module and added packages. This allows you to create modules or applications with a custom structure. In the next part, we will add functionality to the Ordering module. From e3b4c410a0b56c7400ce86104eccf21624b9228c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 15:01:58 +0300 Subject: [PATCH 5/8] Grammer fixes in part 05 --- docs/en/tutorials/modular-crm/part-05.md | 56 ++++++++++++------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-05.md b/docs/en/tutorials/modular-crm/part-05.md index 26466058558..f7d624bcbf3 100644 --- a/docs/en/tutorials/modular-crm/part-05.md +++ b/docs/en/tutorials/modular-crm/part-05.md @@ -14,13 +14,13 @@ } ```` -In the previous part, we've created a custom Ordering module and installed it into the main application. However, the Ordering module has no functionality now. In this part, we will create an `Order` entity and add functionality to create and list the orders. +In the previous part, we created a custom Ordering module and installed it into the main application. However, the Ordering module has no functionality now. In this part, we will create an `Order` entity and add functionality to create and list the orders. ## Creating an `Order` Entity Open the `ModularCrm.Ordering` .NET solution in your IDE. -> Tip: You can open the folder of a module's .NET solution by right-clicking the related module in ABP Studio, selecting the *Open with* -> *Explorer* command. +> Tip: You can open the folder of a module's .NET solution by right-clicking the related module in ABP Studio and selecting the *Open with* -> *Explorer* command. The following figure shows the `ModularCrm.Ordering` module in the *Solution Explorer* panel of Visual Studio: @@ -32,9 +32,9 @@ As you see in the preceding figure, the solution structure is very minimal. It a ![visual-studio-ordering-ui-package-dependency](images/visual-studio-ordering-ui-package-dependency.png) -We will create an [entity class](../../framework/architecture/domain-driven-design/entities.md) and ABP defines base entity classes and other related infrastructure in the [`Volo.Abp.Ddd.Domain`](https://abp.io/package-detail/Volo.Abp.Ddd.Domain) package. So, we need to add a reference to that NuGet package. +We will create an [entity class](../../framework/architecture/domain-driven-design/entities.md). ABP defines base entity classes and other related infrastructure in the [`Volo.Abp.Ddd.Domain`](https://abp.io/package-detail/Volo.Abp.Ddd.Domain) package. So, we need to add a reference to that NuGet package. -You can add that package and arrange the [module](../../framework/architecture/modularity/basics.md) class dependency manually. However, here we will use ABP Studio as a more practical way. +You can add that package and manually arrange the [module](../../framework/architecture/modularity/basics.md) class dependency. However, we will use ABP Studio as a more practical option. Return to ABP Studio, right-click the `ModularCrm.Ordering` package in the *Solution Explorer* and select the *Add Package Reference* command: @@ -44,9 +44,9 @@ That command opens a dialog to add a new package reference: ![abp-studio-add-nuget-package-reference](images/abp-studio-add-nuget-package-reference.png) -Select the *NuGet* tab, type `Volo.Abp.Ddd.Domain` as the *Package name* and write the version of the package you want to install. Please be sure that you are installing exactly the same version with the other ABP packages you are already using. In future versions, ABP Studio will provide an easier way to select the package and its version. +Select the *NuGet* tab, type `Volo.Abp.Ddd.Domain` as the *Package name* and write the version of the package you want to install. Please be sure that you are installing the same version as the other ABP packages you are already using. ABP Studio will provide an easier way to select the package and its version in future ABP versions. -Click the *OK* button and then you can check the *Packages* under the `ModularCrm.Ordering` module *Dependencies* to see the `Volo.Abp.Ddd.Domain` package is installed: +Click the *OK* button. Now you can check the *Packages* under the `ModularCrm.Ordering` module *Dependencies* to see the `Volo.Abp.Ddd.Domain` package is installed: ![abp-studio-added-ddd-domain-package](images/abp-studio-added-ddd-domain-package.png) @@ -70,11 +70,11 @@ namespace ModularCrm.Ordering.Entities } ```` -We are allowing to place only a single product within an order. In a real-world application, the `Order` entity would be much more complex. However, the complexity of the `Order` entity doesn't effect modularity, so we keep it simple to focus on modularity in this tutorial. We are inheriting from the [`CreationAuditedAggregateRoot` class](../../framework/architecture/domain-driven-design/entities.md) since I want to know when an order has been created and who has created it. +We allow users to place only a single product within an order. The `Order` entity would be much more complex in a real-world application. However, the complexity of the `Order` entity doesn't affect modularity, so we keep it simple to focus on modularity in this tutorial. We are inheriting from the [`CreationAuditedAggregateRoot` class](../../framework/architecture/domain-driven-design/entities.md) since I want to know when an order has been created and who has created it. ### Adding an `OrderState` Enumeration -We used an `OrderState` enumeration that is not defined yet. Open an `Enums` folder in the `ModularCrm.Ordering.Contracts` project and create an `OrderState.cs` file inside it: +We used an `OrderState` enumeration that has not yet been defined. Open an `Enums` folder in the `ModularCrm.Ordering.Contracts` project and create an `OrderState.cs` file inside it: ````csharp namespace ModularCrm.Ordering.Contracts.Enums; @@ -87,7 +87,7 @@ public enum OrderState : byte } ```` -Final structure of the Ordering module should be similar to the following figure in your IDE: +The final structure of the Ordering module should be similar to the following figure in your IDE: ![visual-studio-order-entity](images/visual-studio-order-entity.png) @@ -97,7 +97,7 @@ The `Order` entity has been created. Now, we need to configure the database mapp ### Installing the Entity Framework Core Package -> In this section, we will install the [`Volo.Abp.EntityFrameworkCore`](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore) package to the Ordering module. That package is DBMS-independent and leaves the DBMS selection to the final application. If you want, you can install DBMS-specific package instead. For example, you can install the [`Volo.Abp.EntityFrameworkCore.SqlServer`](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore.SqlServer) package if you are using SQL server and want to make SQL Server specific configuration for your module's database. +> In this section, we will install the [`Volo.Abp.EntityFrameworkCore`](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore) package to the Ordering module. That package is DBMS-independent and leaves the DBMS selection to the final application. If you want, you can install a DBMS-specific package instead. For example, you can install the [`Volo.Abp.EntityFrameworkCore.SqlServer`](https://abp.io/package-detail/Volo.Abp.EntityFrameworkCore.SqlServer) package if you are using SQL Server and want to make SQL Server specific configuration for your module's database. > You can search for other packages on the [abp.io/packages](https://abp.io/packages) page. Stop the web application if it is still running. Return to ABP Studio, right-click the `ModularCrm.Ordering` package on the *Solution Explorer* panel and select the *Add Package Reference* command: @@ -112,7 +112,7 @@ Once you click the *OK* button, the NuGet package reference is added. ### Defining the Database Mappings -Entity Framework Core requires to define a `DbContext` class as the main object for the database mapping. We want to use the main application's `DbContext` object. In that way, we can control the database migrations in a single point, ensure database transactions on multi-module operations and establish relations between database tables of different modules. However, the Ordering module can not use the main application's `DbContext` object, because it doesn't depend on the main application and we don't want to establish such a dependency. +Entity Framework Core requires defining a `DbContext` class as the main object for the database mapping. We want to use the main application's `DbContext` object. That way, we can control the database migrations at a single point, ensure database transactions on multi-module operations, and establish relations between database tables of different modules. However, the Ordering module can not use the main application's `DbContext` object because it doesn't depend on the main application, and we don't want to establish such a dependency. As a solution, we will define a `DbContext` interface in the Ordering module which is then implemented by the main module's `DbContext`. @@ -132,7 +132,7 @@ namespace ModularCrm.Ordering.Data } ```` -Now, we can inject and use the `IOrderingDbContext` in the Ordering module. Actually, we will not directly use that interface most of the time. Instead, we will use ABP's [repositories](../../framework/architecture/domain-driven-design/repositories.md). +We can inject and use the `IOrderingDbContext` in the Ordering module. However, we will not usually directly use that interface. Instead, we will use ABP's [repositories](../../framework/architecture/domain-driven-design/repositories.md), which internally uses that interface. It is best to configure the database table mapping for the `Order` entity in the Ordering module. We will create an extension method that will be called by the main application later. Create a class named `OrderingDbContextModelCreatingExtensions` in the same `Data` folder: @@ -155,7 +155,7 @@ namespace ModularCrm.Ordering.Data //Configure table name b.ToTable("Orders"); - //Always call this method to setup base entity properties + //Always call this method to set base entity properties b.ConfigureByConvention(); //Properties of the entity @@ -176,7 +176,7 @@ Open the main application's solution in your IDE, find the `ModularCrmDbContext` [ReplaceDbContext(typeof(IOrderingDbContext))] ```` -`ReplaceDbContext` attribute makes it possible to use the `ModularCrmDbContext` class in the services in the Ordering module. +The `ReplaceDbContext` attribute allows the use of the `ModularCrmDbContext` class in the services in the Ordering module. **(2)** Implement the `IOrderingDbContext` by the `ModularCrmDbContext` class: @@ -204,11 +204,11 @@ protected override void OnModelCreating(ModelBuilder builder) } ```` -In this way, `ModularCrmDbContext` can be used by the Ordering module over the `IProductsDbContext` interface. This part is only needed for one time for a module. Next time, you can just add a new database migration as explained in the next section. +In this way, the Ordering module can use' ModularCrmDbContext' over the `IProductsDbContext` interface. This part is only needed once for a module. Next time, you can add a new database migration, as explained in the next section. #### Add a Database Migration -Now, we can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but we will use ABP Studio's shortcut UI in this tutorial. +Now, we can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but in this tutorial, we will use ABP Studio's shortcut UI. Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command. @@ -220,7 +220,7 @@ The *Add Migration* command opens a new dialog to get a migration name: ![abp-studio-entity-framework-core-add-migration-order](images/abp-studio-entity-framework-core-add-migration-order.png) -Once you click the *OK* button, a new database migration class is added into the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: +Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `ModularCrm.EntityFrameworkCore` project: ![visual-studio-new-migration-class-2](images/visual-studio-new-migration-class-2.png) @@ -234,7 +234,7 @@ After the operation completes, you can check your database to see the new `Order ## Creating the User Interface -Since this is a non-layered module, we can directly use entities and repositories on the user interface. If you think that is not a good practice, then use the layered module template as we've already done for the *Products* module. But for the Ordering module, we will keep it very simple for this tutorial to show it is also possible. +Since this is a non-layered module, we can use entities and repositories directly on the user interface. If you think that is not a good practice, then use the layered module template as we've already done for the *Products* module. But for the Ordering module, we will keep it very simple for this tutorial to show it is also possible. ### Creating a `_ViewImports.cshtml` File @@ -247,7 +247,7 @@ Open the `ModularCrm.Ordering` .NET solution in your favorite IDE, locate the `M @addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling ```` -That file simply imports some tag helpers from ASP.NET Core and ABP. The `Pages` folder should be like the following figure: +That file imports some tag helpers from ASP.NET Core and ABP. The `Pages` folder should be like the following figure: ![visual-studio-pages-folder](images/visual-studio-pages-folder.png) @@ -284,7 +284,7 @@ namespace ModularCrm.Ordering.Pages.Orders } ```` -Here, we are injecting a repository to query `Order` entities from database to show on the page. Open the `Index.cshtml` file and replace the content with the following code block: +Here, we are injecting a repository to query `Order` entities from the database to show on the page. Open the `Index.cshtml` file and replace the content with the following code block: ````html @page @@ -308,7 +308,7 @@ Here, we are injecting a repository to query `Order` entities from database to s ```` -This page simply shows a list of orders on the UI. We haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../book-store/index.md). +This page shows a list of orders on the UI. We haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../book-store/index.md). ### Creating Some Sample Data @@ -320,21 +320,21 @@ You can get `ProductId` values from the `Products` table and [generate](https:// ### Building the Application -Now, we will run the application to see the result. First of all, stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm.Web` application, select the *Build* -> *Graph Build* command: +Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm.Web` application, and select the *Build* -> *Graph Build* command: ![abp-studio-solution-runner-graph-build](images/abp-studio-solution-runner-graph-build.png) -We've performed a graph build since we've made a change on a module and only building the main application is not enough. *Graph Build* command also builds the depended modules if it is necessary. Alternatively, you could build the Ordering module first (on ABP Studio or on your IDE), then Build right-click the `ModularCrm.Web` application and select the *Run* -> *Build & Start*. This approach can be faster if you have too many modules and you made change in one of the modules. +We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE), then right-click the `ModularCrm.Web` application and select the *Run* -> *Build & Start*. This approach can be faster if you have too many modules and you make a change in one of the modules. ### Running the Application -Run the main application on ABP Studio, manually type `/Orders` to end of your application's URL to open the *Orders* page: +Run the main application on ABP Studio, manually type `/Orders` to the end of your application's URL to open the *Orders* page: ![abp-studio-solution-runner-orders-page](images/abp-studio-solution-runner-orders-page.png) Great! We can see the list of orders. However, there are two problems: -1. We don't see the Orders item on the main menu. This is because we haven't configured the [navigation menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) yet. +1. The Order page has no menu item on the main menu. This is because we haven't configured the [navigation menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) yet. 2. We see Product's GUID ID instead of its name. This is because the Ordering module has no integration with the Products module and doesn't have access to Product module's database to perform a JOIN query. We will solve the second problem in the [next part](part-06.md), but we can easily add a menu item for the Orders page now. @@ -373,7 +373,7 @@ namespace ModularCrm.Ordering } ```` -`OrderingMenuContributor` implements the `IMenuContributor` interface which forces us to implement the `ConfigureMenuAsync` method. In that method, we can just manipulate the menu items (add new menu items, remove existing menu items or change properties of existing menu items). The `ConfigureMenuAsync` method is executed whenever the menu is rendered on the UI, so you can dynamically decide how to manipulate the menu items. +`OrderingMenuContributor` implements the `IMenuContributor` interface, which forces us to implement the `ConfigureMenuAsync` method. In that method, we can manipulate the menu items (add new menu items, remove existing menu items or change the properties of existing menu items). The `ConfigureMenuAsync` method is executed whenever the menu is rendered on the UI, so you can dynamically decide how to manipulate the menu items. After creating such a class, we should configure the `AbpNavigationOptions` to add that contributor. Open the `OrderingWebModule` class in the `ModularCrm.Ordering` project and add the following configuration code into the `ConfigureServices` method (if there is no `ConfigureServices` method, first create it as shown below): @@ -397,6 +397,6 @@ The *Orders* menu item is added under the *Products* menu item. ## Summary -In this part of the *Modular CRM* tutorial, we've built the functionality inside the Ordering module that we'd created in the [previous part](part-04.md). Since we've created the Ordering module from scratch (with the *Empty Module* template), we had to implement many aspects manually, added ABP packages, created some configuration classes, etc. It is good to do all these manually for one time for learning the things, but it is better to use the other module templates (that pre-configures the fundamentals for us) for a more comfortable development experience. +In this part of the *Modular CRM* tutorial, we've built the functionality inside the Ordering module we created in the [previous part](part-04.md). Since we've created the Ordering module from scratch (with the *Empty Module* template), we had to implement many aspects manually, add ABP packages, create some configuration classes, etc. It is good to do all these manually for one time to learn the things, but it is better to use the other module templates (that pre-configure the fundamentals for us) for a more comfortable development experience. -In the next part, we will work for establishing communication between the Orders module and the Products module. \ No newline at end of file +In the next part, we will work on establishing communication between the Orders module and the Products module. From 90d2aa92af8ab434011892758d272ec0905bc739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 15:15:54 +0300 Subject: [PATCH 6/8] Grammer fixes for part 6 --- docs/en/tutorials/modular-crm/part-06.md | 38 ++++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-06.md b/docs/en/tutorials/modular-crm/part-06.md index 04f02517c7c..2fd5d82f349 100644 --- a/docs/en/tutorials/modular-crm/part-06.md +++ b/docs/en/tutorials/modular-crm/part-06.md @@ -14,13 +14,13 @@ } ```` -In the previous parts, we've created two modules: The Products module to store and manage products and the Orders module to accept orders. However, these modules were completely independent from each other. Only the main application brings them together to execute in the same application, but these modules don't communicate to each other. +In the previous parts, we created two modules: the Products module to store and manage products and the Orders module to accept orders. However, these modules were completely independent from each other. Only the main application brought them together to execute in the same application, but these modules don't communicate with each other. In the next three parts, you will learn to implement three patterns for integrating these modules: -1. The Order module will make request to the Products module to get product information when needed. -2. The Product module will listen to events from the Orders module, so it can decrease stock count of a product when an order is placed. -3. Finally, we will execute a database query that includes product and order data together. +1. The Order module will make a request to the Products module to get product information when needed. +2. The Product module will listen to events from the Orders module, so it can decrease a product's stock count when an order is placed. +3. Finally, we will execute a database query that includes product and order data. Let's begin from the first one: The Integration Services. @@ -77,15 +77,15 @@ namespace ModularCrm.Products.Integration > > You may think if we can use the existing application services (like `IProductAppService`) from other modules instead of creating specific integration services. Technically you can use, ABP has no restriction. However, from good design and best practice points, we don't suggest it. Because, application services are designed to be consumed specifically by the presentation layer. They will have different authorization and validation logic, they will need different DTO input and output properties, they will have different performance, optimization and caching requirements, and so on. And most importantly, all these will change by the time based on UI requirements and these changes may break your integrations later. It is best to implement specific integration APIs that is designed and optimized for that purpose. > -> We've reused the `ProductDto` object that was actually created for `IProductAppService`. That can be reasonable from the maintenance point. But, if you think your integration service results can be different than the application service results in the future, it can be good to separate them from the first day, so you don't need to introduce breaking changes later. +> We've reused the `ProductDto` object created for `IProductAppService`, which can be reasonable from a maintenance point of view. But if you think your integration service results will be different from the application service results in the future, it can be good to separate them from the first day so you don't need to introduce breaking changes later. ### Implementing the `ProductIntegrationService` Class -We've defined the integration service interface. Now, we can implement it in the `ModularCrm.Products.Application` project. Create an `Integration` folder and then create a `ProductIntegrationService` class in that folder. The final folder structure should be like that: +We've defined the integration service interface. Now, we can implement it in the `ModularCrm.Products.Application` project. Create an `Integration` folder and then create a `ProductIntegrationService` class in that folder. The final folder structure should be like this: ![visual-studio-product-integration-service-implementation](images/visual-studio-product-integration-service-implementation.png) -Open the `ProductIntegrationService.cs` file and replace it's content with the following code block: +Open the `ProductIntegrationService.cs` file and replace its content with the following code block: ````csharp using System; @@ -121,11 +121,11 @@ namespace ModularCrm.Products.Integration The implementation is pretty simple. Just using a [repository](../../framework/architecture/domain-driven-design/repositories.md) to query `Product` [entities](../../framework/architecture/domain-driven-design/entities.md). -> Here, we directly used `List` classes, but instead you could wrap inputs and outputs into [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md). In that way, it can be possible to add new properties to these DTOs without changing the signature of your integration service method (so, without introducing breaking change for your client modules). +> Here, we directly used `List` classes, but instead, you could wrap inputs and outputs into [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md). In that way, it can be possible to add new properties to these DTOs without changing the signature of your integration service method (and without introducing breaking changes for your client modules). ## Consuming the Products Integration Service -The Product Integration Service is ready to be consumed by the other modules. In this section, we will use it in the Ordering module to convert product Ids to product names. +The Product Integration Service is ready for the other modules to use. In this section, we will use it in the Ordering module to convert product IDs to product names. ### Adding a Reference to the `ModularCrm.Products.Application.Contracts` Package @@ -137,9 +137,9 @@ In the opening dialog, select the *This solution* tab, find and check the `Modul ![abp-studio-add-package-reference-dialog-3](images/abp-studio-add-package-reference-dialog-3.png) -ABP Studio adds the package reference and also arranges the [module](../../framework/architecture/modularity/basics.md) dependency. +ABP Studio adds the package reference and arranges the [module](../../framework/architecture/modularity/basics.md) dependency. -> Instead of directly adding such a package reference, it can be best to import the module first (right-click the `ModularCrm.Ordering` module, select the Import Module command and import the `ModularCrm.Products` module), then install the package reference. In that way, it would be easy to see and keep track of inter-module dependencies. +> Instead of directly adding such a package reference, it can be best to import the module first (right-click the `ModularCrm.Ordering` module, select the _Import Module_ command and import the `ModularCrm.Products` module), then install the package reference. In that way, it would be easy to see and keep track of inter-module dependencies. ### Using the Products Integration Service @@ -198,15 +198,15 @@ namespace ModularCrm.Ordering.Pages.Orders Let's see what we've changed: -* Defined a `ProductNames` dictionary. We will use it on the UI to convert product ids to product names. We are filling that dictionary by getting products from the product integration service. -* Injecting `IProductIntegrationService` interface, so we can use it to request products. +* We have defined a `ProductNames` dictionary. We will use it on the UI to convert product IDs to product names. We are filling that dictionary with products from the product integration service. +* Injecting the `IProductIntegrationService` interface so we can use it to request products. * In the `OnGetAsync` method; * First getting the orders from the ordering module's database just like done before. - * Next, we are preparing a unique list of product ids, since the `GetProductsByIdsAsync` methods requests it. + * Next, we are preparing a unique list of product IDs since the `GetProductsByIdsAsync` method requests it. * Then we are calling the `IProductIntegrationService.GetProductsByIdsAsync` method to get a `List` object. - * In the last line, we are converting the product list to a dictionary where the key is `Guid Id` and the value is `string Name`. In that way, we can easily find a product's name with it's id. + * In the last line, we are converting the product list to a dictionary, where the key is `Guid Id` and the value is `string Name`. That way, we can easily find a product's name with its ID. -Open the `Index.cshtml` file, change the `@order.ProductId` part by `@Model.ProductNames[order.ProductId]` to write the product name instead of the product id. The final `Index.cshtml` content should be the following: +Open the `Index.cshtml` file, and change the `@order.ProductId` part by `@Model.ProductNames[order.ProductId]` to write the product name instead of the product ID. The final `Index.cshtml` content should be the following: ````html @page @@ -230,15 +230,15 @@ Open the `Index.cshtml` file, change the `@order.ProductId` part by `@Model.Prod ```` -That's all. Now, you can graph build the main application and run in ABP Studio to see the result: +That's all. Now, you can graph build the main application and run it in ABP Studio to see the result: ![abp-studio-browser-list-of-orders-with-product-name](images/abp-studio-browser-list-of-orders-with-product-name.png) -As you notice, we can see the product names instead of product ids. +As you can see, we can see the product names instead of product IDs. In the way explained in this section, you can easily create integration services for your modules and consume these integration services in any other module. > **Design Tip** > -> It is suggested to keep that type of communication minimum to not couple your modules to each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering module frequently needs to product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Products module. Especially, if you consider to convert your system to a microservice solution in the future, too many direct integration API calls can be a performance bottleneck. +> It is suggested that you keep that type of communication to a minimum and not couple your modules with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering module frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Products module. Especially if you consider converting your system to a microservice solution in the future, too many direct integration API calls can be a performance bottleneck. From cf11f43cf62e5121a6bb58b406c6ede26bfbe970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 15:22:44 +0300 Subject: [PATCH 7/8] Grammer fixes for part 7 --- docs/en/tutorials/modular-crm/part-07.md | 46 ++++++++++++------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-07.md b/docs/en/tutorials/modular-crm/part-07.md index 55d09ae64f2..9dc18648bd4 100644 --- a/docs/en/tutorials/modular-crm/part-07.md +++ b/docs/en/tutorials/modular-crm/part-07.md @@ -14,30 +14,30 @@ } ```` -Another common approach to communicate between modules is messaging. By publishing and handling messages, a module can perform operation when an event happens in another module. +Another common approach to communicating between modules is messaging. By publishing and handling messages, a module can perform an operation when an event happens in another module. ABP provides two types of event buses for loosely coupled communication: * [Local Event Bus](../../framework/infrastructure/event-bus/local/index.md) is suitable for in-process messaging. Since in a modular monolith, both of publisher and subscriber are in the same process, they can communicate in-process, without needing an external message broker. -* **[Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md)** is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default), unless you configure an external message broker. +* **[Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md)** is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. -If you consider to convert your modular monolith to a microservice system later, it is best to use the Distributed Event Bus with default local/in-process implementation. It already supports database level transactional event execution and has no performance penalty. If you switch to an external provider (e.g. [RabbitMQ](../../framework/infrastructure/event-bus/distributed/rabbitmq.md) or [Kafka](../../framework/infrastructure/event-bus/distributed/kafka.md)), you don't need to change your application code. +If you consider converting your modular monolith to a microservice system later, it is best to use the Distributed Event Bus with default local/in-process implementation. It already supports database-level transactional event execution and has no performance penalty. If you switch to an external provider ([RabbitMQ](../../framework/infrastructure/event-bus/distributed/rabbitmq.md), [Kafka](../../framework/infrastructure/event-bus/distributed/kafka.md), etc.), you don't need to change your application code. -On the other hand, if you want to publish events and always subscribe in the same module, you should use the Local Event Bus. In that way, if you switch to microservices later, you don't accidently (and unnecessarily) make a local event distributed. Both of the event bus types can be used in the same system, just understand these and use them properly. +On the other hand, if you want to publish events and always subscribe to the same module, you should use the Local Event Bus. That way, if you switch to microservices later, you don't accidentally (and unnecessarily) distribute a local event. Both event bus types can be used in the same system; just understand them and use them properly. -Since we will use messaging (events) between different modules, we will use the distributed event bus. +We will use the distributed event bus since we will use messaging (events) between different modules. ## Publishing an Event -In the example scenario, we want to publish an event when a new order is placed. The Ordering module will publish the event since it knows when a new order is placed. The Products module will subscribe to that event and get notified when a new order is placed. It will decrease the stock count of the product that is related to the new order. The scenario is pretty simple, let's implement it. +In the example scenario, we want to publish an event when a new order is placed. The Ordering module will publish the event since it knows when a new order is placed. The Products module will subscribe to that event and get notified when a new order is placed. This will decrease the stock count of the product related to the new order. The scenario is pretty simple; let's implement it. ### Defining the Event Class -Open the `ModularCrm.Ordering` module in your IDE, find the `ModularCrm.Ordering.Contracts` project, create an `Events` folder and create an `OrderPlacedEto` class inside that folder. The final folder structure should be like that: +Open the `ModularCrm.Ordering` module in your IDE, find the `ModularCrm.Ordering.Contracts` project, create an `Events` folder and create an `OrderPlacedEto` class inside that folder. The final folder structure should be like this: ![visual-studio-order-event](images/visual-studio-order-event.png) -We've placed the `OrderPlacedEto` class inside the `ModularCrm.Ordering.Contracts` project since that project can be referenced and used by other modules without accessing internal implementation of the Ordering module. The `OrderPlacedEto` class definition should be the following: +We've placed the `OrderPlacedEto` class inside the `ModularCrm.Ordering.Contracts` project since that project can be referenced and used by other modules without accessing the internal implementation of the Ordering module. The `OrderPlacedEto` class definition should be the following: ````csharp using System; @@ -52,19 +52,19 @@ namespace ModularCrm.Ordering.Contracts.Events } ```` -`OrderPlacedEto` is very simple. It is a plain C# class and used to transfer data related to the event (*ETO* is an acronym for *Event Transfer Object*, a suggested naming convention, but not required). You can add more properties if it is needed. For this tutorial, it is more than enough. +`OrderPlacedEto` is very simple. It is a plain C# class used to transfer data related to the event (*ETO* is an acronym for *Event Transfer Object*, a suggested naming convention but not required). You can add more properties if needed, but for this tutorial, it is more than enough. ### Using the `IDistributedEventBus` Service -`IDistributedEventBus` service is used to publish events to the event bus. Until this point, the Ordering module has no functionality to create a new order. +The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering module has no functionality to create new orders. -In the Part 3, we had used ABP's Auto HTTP API Controller feature to automatically expose HTTP APIs from application services. In this section, we will create an ASP.NET Core API controller class to create a new order. In that way, you will also see that it is not different than creating a regular ASP.NET Core controller. +In Part 3, we used ABP's Auto HTTP API Controller feature to expose HTTP APIs from application services automatically. In this section, we will create an ASP.NET Core API controller class to create a new order. In that way, you will also see that it is not different from creating a regular ASP.NET Core controller. Open the `ModularCrm.Ordering` module's .NET solution, create a `Controllers` folder in the `ModularCrm.Ordering` project and place a controller class named `OrdersController` in that new folder. The final folder structure should be like that: ![visual-studio-ordering-controller](images/visual-studio-ordering-controller.png) -Here the full `OrdersController` class: +Here is the full `OrdersController` class: ````csharp using Microsoft.AspNetCore.Mvc; @@ -109,7 +109,7 @@ namespace ModularCrm.Ordering.Controllers // Save it to the database await _orderRepository.InsertAsync(order); - // Publish an event, so other modules can be informed + // Publish an event so other modules can be informed await _distributedEventBus.PublishAsync( new OrderPlacedEto { @@ -132,15 +132,15 @@ namespace ModularCrm.Ordering.Controllers } ```` -The `OrdersController.CreateAsync` method simply creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. +The `OrdersController.CreateAsync` method creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. ## Subscribing to an Event -In this section, we will subscribe to the `OrderPlacedEto` event in the Products module and decrease the related product's stock count once a new order is placed. +This section will subscribe to the `OrderPlacedEto` event in the Products module and decrease the related product's stock count once a new order is placed. ### Adding a Reference to the `ModularCrm.Ordering.Contracts` Package -Since the `OrderPlacedEto` class is located inside the `ModularCrm.Ordering.Contracts` project, we need to add that package's reference to the Products module. This time, we will use the *Import Module* feature of ABP Studio (as an alternative to approach we used in the *Adding a Reference to the `ModularCrm.Products.Application.Contracts` Package* section of the [previous part](part-06.md)). +Since the `OrderPlacedEto` class is in the `ModularCrm.Ordering.Contracts` project, we must add that package's reference to the Products module. This time, we will use the *Import Module* feature of ABP Studio (as an alternative to the approach we used in the *Adding a Reference to the `ModularCrm.Products.Application.Contracts` Package* section of the [previous part](part-06.md)). Open the ABP Studio UI and stop the application if it is already running. Then open the *Solution Explorer* in ABP Studio, right-click the `ModularCrm.Products` module and select the *Import Module* command: @@ -150,11 +150,11 @@ In the opening dialog, find and select the `ModularCrm.Ordering` module, check t ![abp-studio-import-module-dialog-for-ordering](images/abp-studio-import-module-dialog-for-ordering.png) -Once you click the OK button, the Ordering module is imported to the Products module and an installation dialog is open: +Once you click the OK button, the Ordering module is imported to the Products module, and an installation dialog is open: ![abp-studio-install-module-dialog-for-ordering](images/abp-studio-install-module-dialog-for-ordering.png) -Here, select the `ModularCrm.Ordering.Contracts` package on the left side (because we want to add that package reference) and `ModularCrm.Products.Domain` package on the middle area (because we want to add the package reference to that project). We installed it to the [domain layer](../../framework/architecture/domain-driven-design/domain-layer.md) of the Products module since we will create our event handler into that layer. Click the OK button to finish the installation operation. +Here, select the `ModularCrm.Ordering.Contracts` package on the left side (because we want to add that package reference) and `ModularCrm.Products.Domain` package on the middle area (because we want to add the package reference to that project). We installed it on the [domain layer](../../framework/architecture/domain-driven-design/domain-layer.md) of the Products module since we will create our event handler in that layer. Click the OK button to finish the installation operation. You can check the ABP Studio's *Solution Explorer* panel to see the module import and the project reference (dependency). @@ -164,7 +164,7 @@ You can check the ABP Studio's *Solution Explorer* panel to see the module impor Now, it is possible to use the `OrderPlacedEto` class inside the Product module's domain layer since it has the `ModularCrm.Ordering.Contracts` package reference. -Open the Product module's .NET solution in your IDE, locate the `ModularCrm.Products.Domain` project, create a new `Orders` folder and an `OrderEventHandler` class inside that folder. The final folder structure should be like that: +Open the Product module's .NET solution in your IDE, locate the `ModularCrm.Products.Domain` project, and create a new `Orders` folder and an `OrderEventHandler` class inside that folder. The final folder structure should be like this: ![visual-studio-order-event-handler](images/visual-studio-order-event-handler.png) @@ -210,13 +210,13 @@ namespace ModularCrm.Products.Orders } ```` -`OrderEventHandler` implements the `IDistributedEventHandler` interface. In that way, ABP recognizes that class and subscribes to the related event automatically. Implementing `ITransientDependency` simply registers the `OrderEventHandler` class to the [dependency injection](../../framework/fundamentals/dependency-injection.md) system as a transient object. +`OrderEventHandler` implements the `IDistributedEventHandler` interface. In that way, ABP recognizes that class and subscribes to the related event automatically. Implementing `ITransientDependency` registers the `OrderEventHandler` class to the [dependency injection](../../framework/fundamentals/dependency-injection.md) system as a transient object. -We are injecting the product repository and updating the stock count in the event handler method (`HandleEventAsync`). That's all. +We inject the product repository and update the stock count in the event handler method (`HandleEventAsync`). That's it. ### Testing the Order Creation -We will not create a UI for creating an order, to keep this tutorial more focused. You can easily create a form to create an order on your user interface. We will test it just using the Swagger UI in this section. +To keep this tutorial more focused, we will not create a UI for creating an order. You can easily create a form to create an order on your user interface. In this section, we will test it just using the Swagger UI. Graph build the `ModularCrm.Web` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier. @@ -237,7 +237,7 @@ Find the *Orders* API, click the *Try it out* button, enter a sample value the t Once you press the *Execute* button, a new order is created. At that point, you can check the `/Orders` page to see if the new order is shown on the UI, and check the `/Products` page to see if the related product's stock count has decreased. -Here, sample screenshots from the Products and Orders pages: +Here are sample screenshots from the Products and Orders pages: ![products-orders-pages-crop](images/products-orders-pages-crop.png) From 6dcec22149ec69754f56821a464075ad07327e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Sep 2024 15:50:23 +0300 Subject: [PATCH 8/8] Grammer fixes for part 8 --- docs/en/tutorials/modular-crm/part-08.md | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/en/tutorials/modular-crm/part-08.md b/docs/en/tutorials/modular-crm/part-08.md index be760ae06b7..20efa3d7bd4 100644 --- a/docs/en/tutorials/modular-crm/part-08.md +++ b/docs/en/tutorials/modular-crm/part-08.md @@ -10,24 +10,24 @@ } ```` -In this part, you will learn how you can make database-level JOIN operation on data of multiple modules, when their data located in the same physical database. +In this part, you will learn how to perform a database-level JOIN operation on the data of multiple modules when their data is located in the same physical database. ## The Problem -One of the essential purposes of modularity is to create modules those hide (encapsulate) their internal data and implementation details from the other modules. They communicate each other through well-defined [integration services](../../framework/api-development/integration-services.md) and [events](framework/infrastructure/event-bus/distributed). In that way, you can independently develop and change module implementations (even modules' database structures) from each other as long as you don't break these inter-module integration points. +One essential purpose of modularity is to create modules that hide (encapsulate) their internal data and implementation details from the other modules. These modules communicate with each other through well-defined [integration services](../../framework/api-development/integration-services.md) and [events](framework/infrastructure/event-bus/distributed). In that way, you can independently develop and change module implementations (even modules' database structures) from each other as long as you don't break these inter-module integration points. -In a non-modular application, accessing the related data is easy. You could just write a LINQ expression that joins `Orders` and `Products` database tables to get the data with a single database query. It would be easier to implement and executed with a good performance. +In a non-modular application, accessing the related data is easy. You could write a LINQ expression that joins `Orders` and `Products` database tables to get the data with a single database query. It would be easier to implement and execute with a good performance. -On the other hand, it becomes harder to perform operations or get reports those requires to access data of multiple modules in a modular system. Remember the *[Implementing Integration Services](part-06.md)* part; We couldn't access the product data inside the Ordering module (`IOrderingDbContext` only defines a `DbSet`), so we needed to create an integration services just to get names of products. This approach is harder to implement and less performant (yet it is acceptable if you don't show too many orders on the UI or if you properly implement a caching layer), but it gives freedom to the Products module about its internal database or application logic changes. For example, you can decide to move product data to another physical database, or even to another database management system (DBMS) without effecting the other modules. +On the other hand, it becomes harder to perform operations or get reports requiring access to multiple modules' internal data in a modular system. Remember the *[Implementing Integration Services](part-06.md)* part; We couldn't access the product data inside the Ordering module (`IOrderingDbContext` only defines a `DbSet`), so we needed to create an integration service just to get names of products. This approach is harder to implement and less performant (yet it is acceptable if you don't show too many orders on the UI or properly implement a caching layer). Still, it gives freedom to the Products module about its internal database or application logic changes. For example, you can decide to move product data to another physical database or even to another database management system (DBMS) without affecting the other modules. ## A Solution Option -If you want to perform a single database query that spans database tables of multiple modules in a modular system, you have still some options. One option can be creating a reporting module that has access to all of the entities (or database tables). However, when you do that, you accept the following limitations: +If you want to perform a single database query that spans database tables of multiple modules in a modular system, you still have some options. One option can be creating a reporting module with access to all entities (or database tables). However, when you do that, you accept the following limitations: -* When you make changes in a module's database structure, you should also update your reporting code. That is reasonable, but you should be informed by all module developers in such a case. -* You can not change DBMS of a module easily. For example, if you decide to use MongoDB for your Products module while the Ordering module still uses SQL Server, performing such a JOIN operation would not be possible. Even moving Products module to another SQL Server database in another physical server can break your reporting logic. +* When you change a module's database structure, you should also update your reporting code. That is reasonable, but all module developers should let you know in such a case. +* You can not change the DBMS of a module easily. For example, performing such a JOIN operation would be impossible if you decide to use MongoDB for your Products module while the Ordering module still uses SQL Server. Moving the Products module to another SQL Server database in another physical server can also break your reporting logic. -If these are not problems for you, or you can handle when they become problems, you can create reporting modules or aggregator modules that works with multiple modules' data. +If these are not problems for you, or if you can handle them when they become problems, you can create reporting modules or aggregator modules that work with multiple modules' data. In the next section, we will use the main application's codebase to implement such a JOIN operation to keep the tutorial short. However, you already learned how to create new modules, so you can create a new module and develop your JOIN logic inside that new module if you want. @@ -41,7 +41,7 @@ We will define the `IOrderReportingAppService` interface in the `ModularCrm.Appl #### Adding `ModularCrm.Ordering.Contracts` Package Reference -As the first step, we should to add a reference of the `ModularCrm.Ordering.Contracts` package (of the `ModularCrm.Ordering` module) since we will reuse the `OrderState` enum which is defined in that package. +As the first step, we should reference the `ModularCrm.Ordering.Contracts` package (of the `ModularCrm.Ordering` module) since we will reuse the `OrderState` enum defined in that package. Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm.Application.Contracts` package and select the *Add Package Reference* command: @@ -51,11 +51,11 @@ Select the *Imported modules* tab, find and check the `ModularCrm.Ordering.Contr ![abp-studio-add-package-reference-dialog-4](images/abp-studio-add-package-reference-dialog-4.png) -The package reference is added and now we can use the types in the `ModularCrm.Ordering.Contracts` package. +The package reference has been added, and we can now use the types in the `ModularCrm.Ordering.Contracts` package. #### Defining the `IOrderReportingAppService` Interface -Open the main `ModularCrm` .NET solution in your IDE, find the `ModularCrm.Application.Contracts` project, create an `Orders` folder and add an `IOrderReportingAppService` interface inside it. Here the definition of that interface: +Open the main `ModularCrm` .NET solution in your IDE, find the `ModularCrm.Application.Contracts` project, create an `Orders` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface: ````csharp using System.Collections.Generic; @@ -71,7 +71,7 @@ namespace ModularCrm.Orders } ```` -We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that is returned by that method. Create the following class in the same `Orders` folder: +We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the following class in the same `Orders` folder: ````csharp using System; @@ -93,15 +93,15 @@ namespace ModularCrm.Orders } ```` -`OrderReportDto` contains data from both of `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package which defines that enum. +`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum. -After adding these files, the final folder structure should be like that: +After adding these files, the final folder structure should be like this: ![visual-studio-order-reporting-app-service](images/visual-studio-order-reporting-app-service.png) ### Implementing the `OrderReportingAppService` Class -Create an `Orders` folder inside of the `ModularCrm.Application` project and add a class named `OrderReportingAppService` inside it. The final folder structure should be like that: +Create an `Orders` folder inside the `ModularCrm.Application` project and add a class named `OrderReportingAppService` inside it. The final folder structure should be like this: ![visual-studio-order-reporting-app-service-impl](images/visual-studio-order-reporting-app-service-impl.png) @@ -160,11 +160,11 @@ namespace ModularCrm.Orders Let's explain that class; -* It inject [repository](../../framework/architecture/domain-driven-design/repositories.md) services for `Order` and `Product` entities. We can access all entities of all modules from the main application codebase. -* In the `GetLatestOrders` method, we are getting `IQueryable` objects for the entities, so we can create LINQ expressions. -* Then we are executing a LINQ expression with `join` keyword, so we can be able to execute a single query that uses multiple tables. +* It injects [repository](../../framework/architecture/domain-driven-design/repositories.md) services for `Order` and `Product` entities. We can access all entities of all modules from the main application codebase. +* In the `GetLatestOrders` method, we get `IQueryable` objects for the entities so we can create LINQ expressions. +* Then, we execute a LINQ expression with the `join` keyword, enabling us to execute a single query that uses multiple tables. -That's all. In that way, you can execute JOIN queries that uses multiple modules' data. However, if you write majority of your application code into the main application and perform operations on multiple modules, then your system won't be so modular. So, here we show it is technically possible. Please use at your own risk. +That's all. In that way, you can execute JOIN queries that use data from multiple modules. However, if you write most of your code into the main application and perform operations on multiple modules, your system can not be so modular. Here we show it is technically possible. Please use at your own risk. ### Testing the Reporting Service @@ -186,7 +186,7 @@ In the last three parts of this tutorial, you have learned three ways of integra 2. You can publish events from a module and subscribe to these events from other modules. 3. You can write your code into the main application, so you can access to all entities (and related data) of all modules. Instead of writing it into the main application code, you can also create some aggregation or reporting modules that can access more than one module entities. -Now, you know the fundamental principles and mechanics to build sophisticated modular monolith application with ABP. +Now, you know the fundamental principles and mechanics of building sophisticated modular monolith applications with ABP. ## Download the Source Code