.netTiers
Help Wanted! If you are using .netTiers and find it as invaluable as we do, please consider giving back to the .netTiers team by helping with our effort to fully document .netTiers. To help, simply create an account and you will then be able to edit this wiki.
Edit

Business Component Layer

Edit

About: Business Component Layer

The Business Component Layer should be thought of the force field of your application. Ok, maybe not a force field, but any request to the API should go through this layer. The component layer, simply put, defines the boundaries for your entire business logic and data API.

The Component Layer in a nutshell then is where you will find code that controls: business processes, workflow, authorization, application health, and your data API. Not to pigeon hole this layer too much, it's very possible that you will see many other functionalities in this layer, since it not only drives your application, but also is the lifeline of your entire application.

Edit

Component Layer Roles:

Stay Healthy - It's vastly important to always ensure the health of your application. This means that this layer is responsible for the calls going into your API and the data or errors coming back out of the lifeline of your application. It is important that you capture all known errors and handle them appropriately, items such as unhandled exceptions should almost never go back to the consuming layer without the Component Layer knowing about it first. Things you should consider doing, create an exception policy that logs/emails unexpected exceptions. While it's very important that the Component Layer knows about every unhandled exception, it's even more important for you to be aware of it. An application often times can not stay healthy without some type of monitoring by the development team. Authorization - Runtime authorization is very important in an application where the data is sensitive. Take for instance if you were writing an HR application, you would not want to reveal anyone's salary information unless the SecurityPrincipal in question was authorized to. Keeping your authorization in your entry point helps keep your application safe from lurkers. Business Process & Workflow - The crux of any application is to manage the business processes and workflow in a functional and secure manner. Many many technology generations ago, so that makes it 5 years ago, many applications were built with the workflow right in the UI, or the workflow would not account for authorization or security. Our focus as developers has shifted to become more focused on quite a few more requirements of a business application. Meaning, it's extremely important to keep your applications flexible enough for sudden changes in workflow and business processes, while maintaining the proper security and authorization. This is not always a trivial task with some very complex requirements.

Edit

.netTiers Business Component Layers

There are two primary different types of business component layers that .netTiers will generate out of the box for you based on frequent patterns found in enterprise applications. The ServiceLayer and DomainModel, are two patterns that are frequently used when creating and manipulating object domains. There are many things to keep in mind when choosing a Business Component Layer. We will discuss each of them to a further extent, but please read a primer on these patterns.

ServiceLayer Domain Model

Edit

Things To Consider:

Why do I have to use a Business Component Layer? What is a Business Component? Which Business Component implementation is the best? How can I leverage existing code?

Edit

Service Layer

Getting Started: You can use the Service to manage all of your Create, Read, Update, Delete, DeepLoad/Save operations without having to get into creating a pipeline for those requests. We felt we did not want to burden the developer in jumping through many hoops to work with his data repository.

Here's a simple sample that gets all of my accounts from an Accounts Table.

1 AccountService accountsService = new AccountsService(); 2 TList<Accounts> accountList = accountsService.GetAll();


Stepping through the logic, the AccountsService will Authorize the request by the current security context if authorization is enabled. It will check to see the current ConnectionScope, to figure out if a Transaction is opened and which dataProvider to use. There is a simple way to configure the provider you would like to use, be it the sqlClientProvider vs webServiceProvider, multiple configured databases for the same provider type, or dynamic connection strings More on this later.

It will then get the current settings from the ConnectionScope to use to make the correct call for the DataRepository. The call will be sent into the DataRepository and returned back to the ServiceLayer. If there is an exception, HandleException is called and checks to see if you've implemented a custom exception handling scheme, i.e. log & rethrow, stop, have a custom handler.

All of which is done via Configuration in the app/web.config from the ExceptionHandling Capabilities from Enterprise Library. A full sample entlib.config is generated for you when generating the UnitTests for your application. You can get a feel for some of the types of options you have.

Edit

ExceptionHandling Configuration:

1 <exceptionHandling> 2 <exceptionPolicies> 3 <add name="NoneExceptionPolicy"> 4 <exceptionTypes> 5 <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 6 postHandlingAction="None" name="Exception"> 7 <exceptionHandlers> 8 <add logCategory="Exceptions" eventId="100" severity="Error" 9 title="netTiers.Petshop Exception Handling" 10 formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.XmlExceptionFormatter, 11 Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.0.0, Culture=neutral, 12 PublicKeyToken=null" priority="0" 13 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, 14 Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=2.0.0.0, 15 Culture=neutral, 16 PublicKeyToken=null" 17 name="Logging Handler" /> 18 </exceptionHandlers> 19 20 </add> 21 </exceptionTypes> 22 23 </add> 24 </exceptionPolicies> 25 </exceptionHandling>


It's easiest to just point the Enterprise Library Configurator tool to your app/web.config and configure your own custom settings. That way you don't have to mess with the xml yourself.

If there are no exceptions, or you want to continue processing after an exception, the data, your entity or TList<> is returned.

Edit

Service API Example:

1 //Create a new Service Workspace object to work with; 2 AccountService accountsService = new AccountsService(); 3 4 //Create an entity to use in examples; 5 Account accountEntity = new Account(); 6 accountEntity.AccountName = "MyAccountName"; 7 accountEntity.CreatedDate = DateTime.Now; 8 9 //GetAll() 10 TList<Accounts> accountList = accountsService.GetAll(); 11 12 //Find() 13 TList<Accounts> accountList = accountsService.Find("IsActive = 1"); 14 15 //GetPaged() 16 TList<Accounts> accountList = 17 accountsService.GetPaged("IsActive = 1 AND AccountName LIKE 'smi%'"); 18 19 //GetByFk() 20 TList<Accounts> accountList = accountsService.GetByCustomerId(25); 21 22 //GetIX() 23 TList<Accounts> accountList = 24 accountsService.GetByAccountCreatedDate(new DateTime("1/1/2006")); 25 26 27 //Get() 28 Accounts account = accountsService.Get(new AccountsKey(23)); 29 30 //Insert() 31 accountsService.Insert(accountEntity); 32 Response.Write(accountEntity.AccountId); // is now populated 33 34 //Delete() 35 bool result = accountsService.Delete(accountEntity); 36 37 //Delete() 38 bool result = accountsService.Delete(23); 39 40 //Update() 41 accountEntity.AccountName = "MyAccountName 2"; 42 accountsService.Update(accountEntity); 43 44 //GetByManyToManyl() 45 TList<Customers> accountList = accountsService.GetCustomers_From_AccountsReceivable(); 46 47 //GetCustomProcedureName() 48 TList<Accounts> accountList = accountsService.GetByAccountMaturationDate(); 49 50 51 //DeepLoadByIdl() using PK 52 Account account = accountsService.DeepLoadByAccountId( 53 id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(TList<ChartOfAccounts>)); 54 55 //DeepLoadByIdl() using FK 56 TList<Account> account = accountsService.DeepLoadByCustomerId( 57 id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(TList<ChartOfAccounts>)); 58 59 //already instatiated objects//DeepLoad 60 accountsService.DeepLoad( 61 myAccountEntity, false,DeepLoadType.IncludeChildren, typeof(Customers), typeof(TList<ChartOfAccounts>)); 62 63 // is now filled 64 Response.Write(accountsService.CustomerIdSource.LastName); 65 66 // is now filled 67 Response.Write(accountsService.ChartOfAccountsCollection.Count); 68 69 //DeepSave 70 accountsService.DeepSave( 71 myAccountEntity, false, DeepSaveType.IncludeChildren, typeof(Customers), typeof(TList<ChartOfAccounts>)); 72


Edit

Workflow Pipeline:

A Service instance serves as a workspace for implementing your types' behavior. If you were creating a complex Order system, you would have a CreateOrder behavior that would have to do many things. Let's take this scenario as an example and see how we could leverage the workflow pipeline.

Edit

Architecture Example:

Take for example a complex b2b and b2c commerce site. There might be several different
  • check the inventory,
  • validate employee,
  • check employee pricing
  • discover and calculate vendor shipping from 3rd party for a given set of weight of all the products.

Edit

Processors:

A Processor is considered a logical unit of work and you are responsible for creating the processors or behavior for your application. Now, it would be overkill if you had to create processors for CRUD as well, so those methods are exposed as part of the API for each service.

More information on workflow management: http://www.enterpriseintegrationpatterns.com/ProcessManager.html

Information about the Processor Command: http://www.dofactory.com/Patterns/PatternCommand.aspx

Most Flexible, when using a Strategy for each processor. http://www.dofactory.com/Patterns/PatternStrategy.aspx

Edit

Logical Process Flow:

Every Service, is an instance based workspace for managing your application. ex: OrdersService ordersService = new OrdersService(); In that workspace there is a Pipeline framework for you to add multiple processors to conduct your Units of Work.

Every processor is responsible for a single logical business action item(Unit Of Work).

One processor might be to make several checks to ensure the order is valid. EX: ValidateInventoryProcessor(Order o) 1. Find if the products in the order are in inventory. 2. Find out which warehouses these items belong to. 3. Calculate a route for supply chain to get these items. 4. Fire off notifications to warehousing, etc.

You would create other processors to ValidateEmployee, Check Employee Processing, Get Vendor Shipping Information, Billing, Notifications.

Example

1ordersService.ProcessorList.Add(new InventoryProcessor(o)); 2ordersService.ProcessorList.Add(new VerifyEmployeeProcessor(o.EmployeeIdSource)); 3ordersService.ProcessorList.Add(new EmployeeOrderProcessor(o)); 4ordersService.ProcessorList.Add(new BillingProcessor(o)); 5ordersService.ProcessorList.Add(new ShippingProcessor(o)); 6ordersService.ProcessorList.Add(new OrderNotificationProcessor(o));


Every processor will return a class that implements IProcessorResult, which there is a GenericProcessorResult created one for you.

Basically, this class is responsible for tracking processor state and aggregates all the BrokenRules or that the process has accumulated. By tracking state here, you know where exactly the pipeline failed. If you do not track state inside the process, the Service Pipeline will attempt to do so for you.

1 //Execute Processor List 2 ServiceResult result = ordersService.Execute();


Events will fire before and after any processor execution. Every processor result will be aggregated into the ServiceResult class. This will let you all errors that occured, as well as any Exceptions that fired. If an unhandled exception has occured you can stop execution if you set it to AbortOnFailure.

1if (result.HasErrors) 2{ 3 ShowErrors(result.Error); 4 ShowExceptions(result.Exceptions); 5}


If there are errors, you can tap into the ServiceResult's Error property which is a newline delimeted list of all the errors from entity validation. I've attached the class diagram in hopes that this would make more sense. I'm hoping to finish up a sample app for the community very soon.

For basic CRUD, you could still access it normally, but in most of the complex logic, this would lie inside of a processor.

1 ordersService.GetAll(); 2 3 ordersService.Save(o);




Edit

Processor Example:

An InventoryProcessor sample class.

1public class InventoryProcessor : ProcessorBase 2 3{ 4 private Entities.Orders order; 5 private GenericProcessorResult genericProcessorResult; 6 7 8 /// <summary> 9 /// Inventory Processor 10 /// </summary> 11 /// <param name="order">Order to process through Inventory</param> 12 13 public InventoryProcessor(Entities.Orders order) 14 { 15 if (order == null) 16 throw new ArgumentNullException("order"); 17 18 this.order = order; 19 } 20 21 /// <summary> 22 /// Process the IProcessResult 23 /// </summary> 24 /// <returns></returns> 25 public override IProcessorResult Process() 26 { 27 try 28 { 29 ///Add custom validation rules for this processor 30 order.AddInventoryRules(); 31 32 ProductsService products = new ProductsService(); 33 34 //check stock 35 foreach (OrderDetails item in order.OrderDetailsCollection) 36 { 37 item.ProductIDSource = 38 products.Get(new ProductsKey(item.ProductID)); 39 } 40 41 order.Validate(); 42 43 if (!order.IsValid) 44 ProcessResult.AddBrokenRulesList(typeof(Entities.Orders),order.BrokenRulesList); 45 } 46 catch(Exception exc) 47 { 48 if (DomainUtil.HandleException(exc, "NoneExceptionPolicy")) 49 throw; 50 } 51 52 return ProcessResult; 53 } 54 55 /// <summary> 56 /// ProcessResult of this current process to check Inventory on the Order 57 /// </summary> 58 public override IProcessorResult ProcessResult 59 { 60 get 61 { 62 if (genericProcessorResult == null) 63 { 64 genericProcessorResult = new GenericProcessorResult(); 65 } 66 return genericProcessorResult; 67 } 68 } 69}




Edit

Custom Business Rule:

Orders.cs

1 /// <summary> 2 /// Add Extra Custom Validation Rules 3 /// </summary> 4 public void AddInventoryRules() 5 { 6 ValidationRules.AddRule(InventoryRuleCheck, 7 new ValidationRuleArgs("UnitsInStock")); 8 } 9 10 11 /// <summary> 12 /// Check Inventory 13 /// </summary> 14 /// <param name="target"></param> 15 /// <param name="e"></param> 16 /// <returns></returns> 17 public bool InventoryRuleCheck(object target, ValidationRuleArgs e) 18 { 19 foreach(OrderDetails detail in OrderDetailsCollection) 20 { 21 if (detail.ProductIDSource == null) 22 continue; 23 24 if (detail.ProductIDSource.UnitsInStock < detail.Quantity) 25 { 26 e.Description = 27 string.Format("{0} - we do not have that much stock for this.", 28 detail.ProductIDSource.ProductName); 29 return false; 30 } 31 } 32 return true; 33 }


Program.cs

1using System; 2using System.Collections.Generic; 3using System.Text; 4using Northwind.Data; 5using Northwind.Entities; 6using Northwind.Entities.Validation; 7using Northwind.Services; 8using Northwind.Services.Processors.Orders; 9 10namespace NorthwindWebConsole 11{ 12 class Program 13 { 14 15 static void Main(string[] args) 16 { 17 ///Create an Orders Workspace 18 OrdersService service = new OrdersService(); 19 20 ///Create a simulated Order 21 Orders o = new Orders(); 22 ProductsService products = new ProductsService(); 23 TList<Products> plist = products.GetAll(); 24 25 ///Create a fake order to test our validation. 26 for(int i=0;i<10;i++) 27 { 28 OrderDetails detail = new OrderDetails(); 29 detail.ProductID = plist[i].ProductID; 30 detail.ProductIDSource = plist[i]; 31 32 ///Should trigger an invalid quantity on final loops 33 detail.Quantity = Convert.ToInt16(i * i); 34 o.OrderDetailsCollection.Add(detail); 35 } 36 37 ///For this business process, we want to verify Inventory 38 service.ProcessorList.Add(new InventoryProcessor(o)); 39 40 ///An object holding the results of the pipeline request. 41 ServiceResult result = service.Execute(); 42 43 if (result.HasErrors) 44 Console.WriteLine(result.Error); 45 46 Console.ReadLine(); 47 } 48 } 49}


ScrewTurn Wiki version 2.0.31.