Consider from a performance point of view, options for locating logic to populate a model for three-tier and four-tier architectures when using different interaction technologies between levels on the .NET stack (Web API, Web API OData, WCF net.tcp, WCF Data Services).
Preamble
Often, when automating something, an approach to designing software based on a
domain is used , the so-called
Domain Driven Design . The essence of this approach is the creation of a program model of the domain containing the description of the objects of the domain, their relationships and possible operations with them. The presence of such a model makes it convenient to automate complex business processes.
')
As Fowler wrote in
"Enterprise Software Architecture"The value of the domain model is that, having mastered the approach, you have at your disposal a number of techniques that allow you to get along with the increasing complexity of business logic in a “civilized” way.
This design approach is object-oriented, so as a result we get a set of repository classes for performing CRUD and other operations on domain objects and DTO for transferring data from these objects.
Often these classes are implemented as a web service and allocated to a separate level (let's call it AppServer), with which different levels of display interact - web servers, mobile clients, etc. A complete implementation of this approach can be seen in the Pluralsight
Building End-to-End Multi-Client Service Oriented Applications course.
RemarkNaming the course as it hints that they implement the
SOA architecture. It seems to me that they are implementing it with great stretch, because, for example, in the book
“Microsoft's Guide to Designing Application Architecture” , the main feature of the SOA architecture is clearly marked - service autonomy, the possibility of their distributed deployment, independent maintenance and development. IMHO service must have its own independent data storage to be part of SOA. The guys from Pluralsight just have the access facade to the domain model implemented as a set of WCF services, nothing more.
In other cases, these classes are simply a separate layer with which the presentation layer interacts.
However, almost always DTOs returned by such classes cannot serve as a model for the View UI, since contains either too much or too little data. This is partly due to the fact that the display levels are different (mobile clients, web servers) and they need different UI, although they work with the same domain model. Partly by the fact that just according to the requirements on a specific screen it is necessary to show the data of several objects of the domain.
Thus, it is obvious that the models for the UI are not displayed 1 in 1 on the DTO used to store the data of the domain objects, and therefore they need to be formed somewhere.
And where to form them and why is it important?
As it turns out, the place of their formation, as well as the inter-level interaction technologies used in this process, can easily make a good system a bad one in the eyes of users.
RemarkIt is believed that a good automation system is different from a bad one in that it allows users to do their work faster than before .
Consider, from a performance point of view, the following options for forming a model for display in the UI:
Three-tier architecture (browser + webserver + database server)- The model is formed on the client
- The model is formed on the web server.
Four-tier architecture (browser + webserver + appserver + database server)- The model is formed on the web server.
- The model is formed on the appserver
So, we have 4 basic options.
To make it more interesting, we will use different technologies from the .NET stack for interaction between the levels.
And in order to make it very interesting - in the form of a model we will transfer the “real” amount of data (~ 150kb in uncompressed form), and the browser will be placed away from the main stand (good, the size of our Motherland allows it).
Used technologies and tools
The browser is Chrome 48, the model from the web server will be received in JSON using JQuery 2.
Web server - IIS 6.1, the model will render using Web API 2 (in one of the WebAPI OData v3 scenarios) using gzip compression. JSON serializer standard. The data will be received either from Appserver (in a four-tier architecture), or directly from the database using EF 6 (in a three-tier one).
Appserver - IIS 6.1, data will be given either using WCF Service (Net.TCP binding), or using WCF DataService v3 (without compression). C DB will communicate using EF 6.
Database server - MS SQL 2014 standart.
Level distribution by gland
Browser, web server, appserver, database server - all on different physical machines.
Iron configuration:
- Browser - the usual office computer based on Core i5
- Web server - virtual server based on Xeon E5504, 12 GB
- Appserver - virtual server based on Xeon E5504, 16 GB
- Database server - virtual server based on Xeon E5-2620, 32 GB, Raid 10 SAS
Web server, appserver, database server - in one network, geographically in Moscow. Connected by 10TB network. Webserver sticks to the Internet for Microsoft Forefront TMG.
The browser is outside, geographically in Moscow and Ufa (we will consider two options). Connected to the web server by the usual "good" Internet via WIFI.
Data
The database contains a simple table (Guid, Name) with 10 thousand data lines.
To form a model from this label using EF, three sets of data of 1000 rows are taken starting from an arbitrary value (Skip, Take). The first set is left unchanged, the second and third are filtered in the model's fill code. The total size of the rows of three sets is 2000 objects or ~ 150 KB.
Each data set is returned as a model. In this case, the first set is obtained synchronously, and the second and third - asynchronously.
Thus, both the database and the EF are present in us, but they contribute to the time of data acquisition in a minimum (since we test not the EF and not the database, but the architecture as a whole), but the size of the model is large.
What and how we measure
We measure the time in the browser between the beginning of the query model and the result in the form of javascript objects. Rendering of results in the browser is present, but not measured.
After rendering the model after waiting 1s, we repeat everything again.
Thus, we make ~ 500 measurements and build a graph of the distribution of results as a percentage, depending on time.
We will conduct two series of measurements simultaneously, placing the browsers geographically in different places (Moscow and Ufa)
Measurements will be carried out during working hours when channels and servers are regularly loaded.
Test benches
Thus we form the following test benches
- Four-tier architecture, the model is formed on the web server, the appserver is accessible via WCF DataService, the result is transmitted to the browser via the Web API
- Four-tier architecture, the model is formed on the web server, the appserver is available via the WCF service using the net.tcp protocol, the result is transmitted to the browser via the Web API
- Four-tier architecture, the model is formed on the appserver, which is accessible via the WCF service via the net.tcp protocol, the result is transmitted to the browser via the Web API
- Three-tier architecture, the model is formed on the web server, the result is transmitted to the browser via the Web API
- Three-tier architecture, the model is formed in the browser using a web server, accessible through the Odata Web API
Further description of each model and results, we go from the most brake to the fastest
Four-tier architecture, the model is formed on the web server, the appserver is accessible via WCF DataService, the result is transmitted to the browser via the Web API
Special features
Since the WCF DataService (which runs on top of the EF model) is used to access the application server, it is not necessary to write methods in this service to implement various data retrieval requests. Requests can be made via URL, or using LINQ
Model Data Acquisition Codepublic class DocumentListController : ApiController {
Service code [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class DocumentsService : EntityFrameworkDataService<Documents> { public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.AllRead); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; } }
results
As you can see, almost all requests fall in the interval of 850-1100 ms, and about 5% are executed more than 1.1s. This is for Moscow. For Ufa, the picture is worse.
Conclusion
WCF Data Services is not our option
Three-tier architecture, the model is formed in the browser using a web server, accessible through the Odata Web API
Description
Since the model is formed using the OData Web API (which also works on top of the EF model), to implement various data retrieval requests, it is not necessary to write methods in the Web API controller. Requests can be made via URL.
Since the model is formed in the browser, three Web API requests go to the web server, unlike all other scenarios, where there are only one such requests.
Model formation code (on Type Script) class DocumentItem { Id: string; Name: string; constructor(_id: string, _name: string) { this.Id = _id; this.Name = _name; } } class DocumentsController { DocumentItems: DocumentItem[]; OddDocumentItems: DocumentItem[]; EvenDocumentItems: DocumentItem[]; constructor() { this.DocumentItems = []; this.EvenDocumentItems = []; this.OddDocumentItems = []; } public FillData(): Promise<any> { var p = new Promise<any>((resolve, reject) => { var queryOne = this.ExecuteQuery(this.GetQuery()) .then((data: DocumentItem[]) => { this.DocumentItems = data; var queryTwo = this.ExecuteQuery(this.GetQuery()); var queryThree = this.ExecuteQuery(this.GetQuery()); var result = Promise.all([queryTwo, queryThree]); result .then((data: DocumentItem[][]) => { this.EvenDocumentItems = data[0].filter((x, i, all) => i % 2 != 0); this.OddDocumentItems = data[1].filter((x, i, all) => i % 2 == 0); resolve(); }, reject); }, reject); }); return p; } private GetQuery(): string { var random = Math.floor(Math.random() * 9000); return "$orderby=Id desc&$skip=" + random + "&$top=1000"; } private ExecuteQuery(query: string): Promise<DocumentItem[]> { var uri = "odata/Documents?"; var p = new Promise((resolve, reject) => { $.getJSON(uri + query) .done((data: any) => { var realData = $.map<any, DocumentItem>(data.value, (x: any, i: number) => new DocumentItem(x.Id, x.Name)); resolve(realData); }) .fail((jqXHR: JQueryXHR, textStatus: string, err: string) => reject(err)); }); return p; } }
On the server, the controller code is template (Web API 2 OData v3 Controller with actions using Entity Framework), I will not give it.
results
As you can see, almost all requests fall in the range of 200-350 ms for Moscow and 250-400 ms for Ufa, which is not so bad. BUT, there are also slow queries, more than 800 ms (for Ufa such about 5%). For the user, this will mean that the UI of the system works almost always quickly, but sometimes it will slow down. This is very annoying.
Conclusion
This architecture is now fashionable. And do not say that it is very slow. But IMHO it is suitable only for intranets, only for powerful office machines and only for desktop applications UI. Version, browser type - strongly influence the result. Under IE, all of this works significantly slower, I'm not talking about mobile browsers.
Four-tier architecture, the model is formed on the web server, the appserver is available via the WCF service using the net.tcp protocol, the result is transmitted to the browser via the Web API
Service code public class DocumentsService : IDocumentsService { public DocumentItem[] GetAllDocuments() { var rnd = new Random(); using (var context = new Documents()) { return context.DocumentItems.OrderBy(x => x.Id).Skip(rnd.Next(0, 9000)).Take(1000).ToArray(); } } public DocumentItem[] GetEvenDocuments() { var rnd = new Random(); using (var context = new Documents()) { return context.DocumentItems.OrderBy(x => x.Id).Skip(rnd.Next(0, 9000)).Take(1000).ToList().Where((x, i) => i % 2 != 0).ToArray(); } } public DocumentItem[] GetOddDocuments() { var rnd = new Random(); using (var context = new Documents()) { return context.DocumentItems.OrderBy(x => x.Id).Skip(rnd.Next(0, 9000)).Take(1000).ToList().Where((x, i) => i % 2 == 0).ToArray(); } } }
Result
As you can see, almost all requests fall in the range of 100-200 ms for Moscow and 150-250ms for Ufa. And, most importantly, there are no slow queries for Moscow, for Ufa - almost none.
Conclusion
With tz. performance - uniquely suitable architecture.
Four-tier architecture, the model is formed on the Appserver, which is accessible via the WCF service via the net.tcp protocol, the result is transmitted to the browser via the Web AP
Special features
Many will say that the model for UI to form on the appserver is moveton. So it, in fact, is.
To smooth the situation, we will not expand the facade of the DD model with methods for returning the model to the UI, but create a new service on the appserver that will use the facade of the DD model and form the model for the UI. The web server will use this particular service. If you need to implement a new UI, a new service must be added to the appserver.
In this scenario, the web server sends 1 WCF request to form a model, instead of three for all the other considered four-level scenarios.
Model formation code public class UIService : IUIService { public async Task<DocumentListViewModel> GetModel() { var result = new DocumentListViewModel(); var docService = new DocumentsService(); result.AllDocuments = await Task.Run(() => docService.GetAllDocuments()); var even = Task.Run(() => docService.GetEvenDocuments()); var odd = Task.Run(() => docService.GetOddDocuments()); var evenOdd = await Task.WhenAll(even, odd); result.EvenDocuments = evenOdd[0]; result.OddDocuments = evenOdd[1]; return result; } } }
results
As you can see, almost all requests fall in the range of 100-150 ms for Moscow and 150-250ms for Ufa.
Conclusion
The fastest four-tier architecture. And the most reliable, because the exchange between the web server and the appserver is minimized both by the amount of data and by the call frequency.
Three-tier architecture, the model is formed on the web server, the result is transmitted to the browser via the Web API
Controller code public class DocumentListController : ApiController {
Local repository code (wrapper over EF) public class DocumentsService { public DocumentItem[] GetAllDocuments() { var rnd = new Random(); using (var context = new Documents()) { return context.DocumentItems.OrderBy(x => x.Id).Skip(rnd.Next(0, 9000)).Take(1000).ToArray(); } } public DocumentItem[] GetEvenDocuments() { var rnd = new Random(); using (var context = new Documents()) { return context.DocumentItems.OrderBy(x => x.Id).Skip(rnd.Next(0, 9000)).Take(1000).ToList().Where((x, i) => i % 2 != 0).ToArray(); } } public DocumentItem[] GetOddDocuments() { var rnd = new Random(); using (var context = new Documents()) { return context.DocumentItems.OrderBy(x => x.Id).Skip(rnd.Next(0, 9000)).Take(1000).ToList().Where((x, i) => i % 2 == 0).ToArray(); } } }
Result

As you can see, almost all requests fall in the range of 100-150 ms for Moscow and 100-200 ms for Ufa.
Conclusion
If you don't need a four-tier architecture, then this is the fastest option.
So it goes.