From 378b85acc8611dccab23f0949fcdfd03dcb8d9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 5 Oct 2021 16:47:47 +0300 Subject: [PATCH] add "Static (Generated) Client Proxies for C# and JavaScript" and "Transactional Outbox & Inbox for the Distributed Event Bus" sections. --- .../Blog-Posts/2021-10-05 v5_0_Beta1/POST.md | 125 ++++++++++++++++++ .../2021-10-05 v5_0_Beta1/csharp-proxies.png | Bin 0 -> 4945 bytes .../2021-10-05 v5_0_Beta1/js-proxies.png | Bin 0 -> 5693 bytes 3 files changed, 125 insertions(+) create mode 100644 docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/csharp-proxies.png create mode 100644 docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/js-proxies.png diff --git a/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/POST.md b/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/POST.md index 11c6f59aa9..0bb50a604e 100644 --- a/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/POST.md +++ b/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/POST.md @@ -49,4 +49,129 @@ In this section, I will introduce some major features released with beta 1. ### Static (Generated) Client Proxies for C# and JavaScript +Dynamic C# ([see](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients)) and JavaScript ([see](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Dynamic-JavaScript-Proxies)) client proxy system is one of the most loved features of the ABP Framework. It generates the proxy code on runtime and makes client-to-server calls a breeze. With ABP 5.0, we are providing an alternative approach: You can generate the client proxy code on development-time. + +Development-time client proxy generation has a performance advantage since it doesn't need to obtain the HTTP API definition on runtime. It also makes it easier to consume a (micro)service behind an API Gateway. With dynamic proxies, we should return a combined HTTP API definition from the API Gateway and need to add HTTP API layers of the microservices to the gateway. Static proxies removes this requirement. One disadvantage is that you should re-generate the client proxy code whenever you change your API endpoint definition. Yes, software development always has tradeoffs :) + +Working with static client proxy generation is simple with the ABP CLI. You first need to run the server application, open a command-line terminal, locate to the root folder of your client application, then run the `generate-proxy` command of the ABP CLI. + +#### Creating C# client proxies + +C# client proxies are useful to consume APIs from Blazor WebAssembly applications. It is also useful for microservice to microservice HTTP API calls. Notice that the client application need to have a reference to the application service contracts (typically, the `.Application.Contracts` project in your solution) in order to consume the services. + +**Example usage:** + +````bash +abp generate-proxy -t csharp -u https://localhost:44305 +```` + +`-t` indicates the client type, C# here. `-u` is the URL of the server application. It creates the proxies for the application (the app module) by default. You can specify the module name as `-m ` if you are building a modular system. The following figure shows the generated files: + +![csharp-proxies](csharp-proxies.png) + +Once the proxies are generated, you can inject and use the application service interfaces of these proxies, like `IProductAppService` in this example. Usage is same of the [dynamic C# client proxies](https://docs.abp.io/en/abp/latest/API/Dynamic-CSharp-API-Clients). + +#### Creating JavaScript client proxies + +JavaScript proxies are useful to consume APIs from MVC / Razor Pages UI. It works on JQuery AJAX API, just like the [dynamic JavaScript proxies](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Dynamic-JavaScript-Proxies). + +**Example usage:** + +````bash +abp generate-proxy -t js -u https://localhost:44305 +```` + +The following figure shows the generated file: + +![js-proxies](js-proxies.png) + +Then you can consume the server-side APIs from your JavaScript code just like the [dynamic JavaScript proxies](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Dynamic-JavaScript-Proxies). + +#### Creating Angular client proxies + +Angular developers already knows that the generate-proxy command was already available in ABP's previous versions to create client-side proxy services in the Angular UI. The same functionality continues to be available and already [documented here](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies). + +Example usage: + +````bash +abp generate-proxy -t ng +```` + +URL is not required here. It gets the URL from the application's configuration by default. See [the documentation](https://docs.abp.io/en/abp/latest/UI/Angular/Service-Proxies) for more information. + +### Transactional Outbox & Inbox for the Distributed Event Bus + +This was one of the most awaited features by distributed software developers. + +The [transactional outbox pattern](https://microservices.io/patterns/data/transactional-outbox.html) is used to publishing distributed events within the same transaction that manipulates the application database. Distributed events are saved into the database inside the same transaction with your data changes, then sent to the message broker (like RabbitMQ or Kafka) by a separate background worker with a re-try system. In this way, it ensures the consistency between your database state and the published events. + +The transactional inbox pattern, on the other hand, saves incoming events into database first, then executes the event handler in a transactional manner and removes the event from the inbox queue in the same transaction. It also ensures that the event is only executed one time by keeping the processed messages for a while and discarding the duplicate events received from the message broker. + +Enabling the inbox and outbox patterns requires a few manual steps for your application. We've created a simple [console application example](https://github.com/abpframework/abp/tree/dev/test/DistEvents), but I will also explain all the steps here. + +#### Pre-requirements + +First of all, you need to have EF Core or MongoDB installed into your solution. It should be already installed if you'd created a solution from the ABP startup template. + +#### Install the package + +Install the new [Volo.Abp.EventBus.Boxes](https://www.nuget.org/packages/Volo.Abp.EventBus.Boxes) NuGet package to your database layer (to `EntityFrameworkCore` or `MongoDB` project) or to the host application. Open a command-line terminal at the root directory of your database (or host) project and execute the following command: + +````csharp +abp add-package Volo.Abp.EventBus.Boxes +```` + +This will install the package and setup the ABP module dependency. + +#### Configure the DbContext + +Open your `DbContext` class, implement the `IHasEventInbox` and the `IHasEventOutbox` interfaces. You should end up by adding two `DbSet` properties into your `DbContext` class: + +````csharp +public DbSet IncomingEvents { get; set; } +public DbSet OutgoingEvents { get; set; } +```` + +Add the following lines inside the `OnModelCreating` method of your `DbContext` class: + +````csharp +builder.ConfigureEventInbox(); +builder.ConfigureEventOutbox(); +```` + +It is similar for MongoDB; implement the `IHasEventInbox` and the `IHasEventOutbox` interfaces. There is no `Configure...` method for MongoDB. + +Now, we've added inbox/outbox related tables to our database schema. Now, for EF Core, use the standard `Add-Migration` and `Update-Database` commands to apply changes into your database (you can skip this step for MongoDB). If you want to use the command-line terminal, run the following commands in the root directory of the database integration project: + +````bash +dotnet ef migrations add "Added_Event_Boxes" +dotnet ef database update +```` + +#### Configure the distributed event bus options + +As the last step, write the following configuration code inside the `ConfigureServices` method of your module class: + +````csharp +Configure(options => +{ + options.Outboxes.Configure(config => + { + config.UseDbContext(); + }); + + options.Inboxes.Configure(config => + { + config.UseDbContext(); + }); +}); +```` + +Replace `YourDbContext` with your `DbContext` class. + +That's all. You can continue to publishing and consuming events just as before. See the [distributed event bus documentation](https://docs.abp.io/en/abp/latest/Distributed-Event-Bus) if you don't know how to use it. + +### Publishing events in transaction + TODO + diff --git a/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/csharp-proxies.png b/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/csharp-proxies.png new file mode 100644 index 0000000000000000000000000000000000000000..1805b5238ed390c3beb73f046c18d28ebff615d4 GIT binary patch literal 4945 zcmV-X6RzxuP) ze@q)^n#Z3PXtBHL1w(eR-RssiH2zg7>jbTKH|-UojR>T$is7Vc)5}qOQA0>|L3K_l zB#Ksqvx`cUYm#KnVI)HFM9Oo5fBK3F&=JIsnkBII-JS#gmJ{G4kuo|1!qUq$}AB0Nz#Mu z(W6Iob#)1ew?Hq#)#?PG&R zXf<{?al~#D+wFGy*y&xPt!JnxYAS10w%hG?y9ox((GL;2ud}n--_L%$1VwZ5(i!?N z!958nK^8ZMu~;nczyE%7bMw}15cv?H`+ht)sz1;z zWAQZH!nOjfxvr{UsK+3WdAtO^z(ufGES9>uy5{EQ($dmp?Cy`N<9Lj)MCS*L0~by(c1}6~sOX5?Ee2MyFN+0$lAdvcv!hC_9@}+#j3l?zf{QY8 zC6N}wvaUwb_=>%j5`;jeP=eW4+W8qB{CEn0ib?(UVu;W!44FbX+Ir>^0Kin|NwdE( zrhT`SYa?`vU_ox^88m7flS^49@QkEqwydnIL`51bGr5hL$~7hcj5UXb?9?T7>dItw zS((~|008egZEpbpt#ZPsR*wO|uJ*AKgaBB(g@RB?OS@jPUvnNnV>IevF!CY7_m$Un zw@;|)Zo5v8wG=bVhFI8sQM3O%0AReU-7bs&qads%DA^N{KlvVXWg-g|$%m)gw8 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 P00000NkvXXu0mjfI5UH% literal 0 HcmV?d00001 diff --git a/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/js-proxies.png b/docs/en/Blog-Posts/2021-10-05 v5_0_Beta1/js-proxies.png new file mode 100644 index 0000000000000000000000000000000000000000..4cc9e413084ab9bf1d435161e257edc9144080ed GIT binary patch literal 5693 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 R1n{;nprc`^-m2yp_kZlm{~Q1S literal 0 HcmV?d00001