EditBusiness Component Layer
EditAbout: 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.
EditComponent 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 ModelEditThings 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?
EditService 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.
EditExceptionHandling 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
EditWorkflow 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.
EditArchitecture 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.
EditProcessors:
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.htmlInformation about the Processor Command:
http://www.dofactory.com/Patterns/PatternCommand.aspxMost Flexible, when using a Strategy for each processor.
http://www.dofactory.com/Patterns/PatternStrategy.aspxEditLogical 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
1
ordersService.ProcessorList.Add(new InventoryProcessor(o));
2
ordersService.ProcessorList.Add(new VerifyEmployeeProcessor(o.EmployeeIdSource));
3
ordersService.ProcessorList.Add(new EmployeeOrderProcessor(o));
4
ordersService.ProcessorList.Add(new BillingProcessor(o));
5
ordersService.ProcessorList.Add(new ShippingProcessor(o));
6
ordersService.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.
1
if (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 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);
EditProcessor Example:
An InventoryProcessor sample class.
1
public 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
}
EditCustom 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
1
using System;
2
using System.Collections.Generic;
3
using System.Text;
4
using Northwind.Data;
5
using Northwind.Entities;
6
using Northwind.Entities.Validation;
7
using Northwind.Services;
8
using Northwind.Services.Processors.Orders;
9
10
namespace 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
}