Welcome to the CodeSmith Community!

Re: ComponentModel ServiceLayer Usage

.netTiers

A description has not yet been added to this group.

ComponentModel ServiceLayer Usage

  • rated by 0 users
  • This post has 3 Replies |
  • 3 Followers
  • ClassDiagram1.png
    Intro:
    The ServiceLayer wraps the boundaries for your entire API. All call should enter through
    your ServiceLayer and your service layers main responsibilities are to:
    1. Ensure the health of your application.
    2. Authorize Resources into your Business Domain (if necessary)
    3. Manage business processes and workflow
    4. Be Flexible enough to handle the most complex scenarios
    You can find more information about the ServiceLayer's Pattern found here:
    ServiceLayer
    http://patternshare.org/default.aspx/Home.MF.ServiceLayer

    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 get's all of my accounts from a the Accounts Table.
    AccountService accountsService = new AccountsService();
    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.
    Here is an example of the ExceptionHadling configuration.

        <exceptionHandling>
    <exceptionPolicies>
    <add name="NoneExceptionPolicy">
    <exceptionTypes>
    <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
    postHandlingAction="None" name="Exception">
    <exceptionHandlers>
    <add logCategory="Exceptions" eventId="100" severity="Error"
    title="netTiers.Petshop Exception Handling" formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.XmlExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null"
    priority="0" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null"
    name="Logging Handler" />
    </exceptionHandlers>
    </add>
    </exceptionTypes>
    </add>
    </exceptionPolicies>
    </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.
    Your API Consists of:

    AccountService accountsService = new AccountsService();

    //GetAll() TList<Accounts> accountList = accountsService.GetAll(); //GetPagedl() TList<Accounts> accountList = accountsService.GetPaged("IsActive = 1 AND AccountName LIKE 'smi%'"); //GetByFk() TList<Accounts> accountList = accountsService.GetByCustomerId(25); //GetIX() TList<Accounts> accountList = accountsService.GetByAccountCreatedDate(new DateTime("1/1/2006"));

    //Get() entity.Entitykey; TList<Accounts> accountList = accountsService.Get(entity.EntityKey); //Insert() Account accountEntity = new Account();
    accountEntity.AccountName = "MyAccountName";
    accountEntity.CreatedDate = DateTime.Now;
    TList<Accounts> accountList = accountsService.Insert(accountEntity);

    //Delete() TList<Accounts> accountList = accountsService.Delete(accountEntity); //Delete() TList<Accounts> accountList = accountsService.Delete(23); //Update() accountEntity.AccountName = "MyAccountName 2"; TList<Accounts> accountList = accountsService.Update(accountEntity); //GetByManyToManyl() TList<Customers> accountList = accountsService.GetCustomers_From_AccountsReceivable(); //GetCustomProcedureName() TList<Accounts> accountList = accountsService.GetByAccountMaturationDate(); //DeepLoadByIdl() using PK Account account = accountsService.DeepLoadByAccountId(id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));

    //DeepLoadByIdl() using FK TList<Account> account = accountsService.DeepLoadByCustomerId(id, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));

    //already instatiated objects //DeepLoad TList<Account> account = accountsService.DeepLoad(myAccountEntity, false, DeepLoadType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));

    //DeepSave TList<Account> account = accountsService.DeepSave(myAccountEntity, false, DeepSaveType.IncludeChildren, typeof(Customers), typeof(ChartOfAccounts));


    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.
    In this example, let's say you had to
    *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.

    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://patternshare.org/default.aspx/Home.HW.ProcessManager

    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

    The logical flow is like this:
    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.

    ex
    ordersService.ProcessorList.Add(new InventoryProcessor(o));
    ordersService.ProcessorList.Add(new VerifyEmployeeProcessor(o.EmployeeIdSource));
    ordersService.ProcessorList.Add(new EmployeeOrderProcessor(o));
    ordersService.ProcessorList.Add(new BillingProcessor(o));
    ordersService.ProcessorList.Add(new ShippingProcessor(o));
    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.

    //Execute Processor List
    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.

    if (result.HasErrors)
    {
    ShowErrors(result.Error);
    ShowExceptions(result.Exceptions);
    }

    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.

    ordersService.GetAll();
    ordersService.Save(o);
    Example on Processor Usage:
    Here's a quick InventoryProcessor sample class.

        public class InventoryProcessor : ProcessorBase
    {
    private Entities.Orders order;
    private GenericProcessorResult genericProcessorResult;

    /// <summary> /// Inventory Processor /// </summary> /// <param name="order"></param> public InventoryProcessor(Entities.Orders order)
    {
    this.order = order;
    }

    /// <summary> /// Process the IProcessResult /// </summary> /// <returns></returns> public override IProcessorResult Process()
    {
    try { order.AddInventoryRules(); //check stock order.Validate();

    if (!order.IsValid)
    ProcessResult.AddBrokenRulesList(typeof (Entities.Orders), order.BrokenRulesList);
    }
    catch(Exception exc)
    {
    if (DomainUtil.HandleException(exc, "NoneExceptionPolicy"))
    throw;
    }

    return ProcessResult;
    }

    /// <summary> /// ProcessResult /// </summary> public override IProcessorResult ProcessResult
    {
    get { if (genericProcessorResult == null)
    {
    genericProcessorResult = new GenericProcessorResult();

    }
    return genericProcessorResult;
    }
    }

    Custom Business Rule in Orders.cs
        /// <summary>
        /// Add Extra Custom Validation Rules
        /// </summary>
        public void AddInventoryRules()
    {
    ValidationRules.AddRule(InventoryRuleCheck, new ValidationRuleArgs("UnitsInStock"));
    }

    /// <summary> /// Check Inventory /// </summary> /// <param name="target"></param> /// <param name="e"></param> /// <returns></returns> public bool InventoryRuleCheck(object target, ValidationRuleArgs e)
    {
    foreach(OrderDetails detail in OrderDetailsCollection)
    {
    if (detail.ProductIDSource == null)
    continue;

    if (detail.ProductIDSource.UnitsInStock < detail.Quantity)
    {
    e.Description = string.Format("{0} - is out of stock, {1} units are on order.",
    detail.ProductIDSource.ProductName, detail.ProductIDSource.UnitsOnOrder);
    return false;
    }
    }
    return true;
    }


    Program.cs
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Northwind.Entities;
    using Northwind.Entities.Validation;
    using Northwind.Services;
    using Northwind.Services.Processors.Orders;

    namespace NorthwindWebConsole
    {
    class Program
    {
    static void Main(string[] args)
    {
    //create a fake order
    OrdersService service = new OrdersService();
    Orders o = new Orders();
    ProductsService products = new ProductsService();
    TList<Products> plist = products.GetAll();

    for(int j=0;j<10;j++)
    {
    OrderDetails detail = new OrderDetails();
    detail.ProductID = plist[j].ProductID;
    detail.ProductIDSource = plist[j];
    detail.Quantity = Convert.ToInt16(j * j);
    o.OrderDetailsCollection.Add(detail);
    }

    service.ProcessorList.Add(new InventoryProcessor(o));
    ServiceResult result = service.Execute();
    if (result.HasErrors)
    Console.WriteLine(result.Error);


    Console.ReadLine();
    }
    }
    }


    Robert Hinojosa
    -------------------------------------
    Member of the Codesmith Tools, .netTiers, teams
    http://www.nettiers.com
    -------------------------------------

  • This is very cool, Robert! Thanks for great introduction to Component Layer! I've noticed that Component Layer is not being used in Petshop sample?
    Best regards,
    Alex.
  • Hi Robert!

    This is great documentation!

    Two things:

    1) Would it be possible to get a higher resolution version of ClassDiagram1.png that would be more legible?

    2) Let's say you would have an order.aspx form (with its code-behind order.aspx.cs) to enter the order information, where would you put the follwing code (taken from your Main() above)?
            service.ProcessorList.Add(new InventoryProcessor(o));
    ServiceResult result = service.Execute();
    if (result.HasErrors)
    Console.WriteLine(result.Error);

    And what would you replace Console.WriteLine(result.Error) with in order to dipsplay the error message on the web form?

    Best regards,

    Jean-François

    P.S. I don't know it it makes any difference, but for 2, suppose we are using a strongly typed datasource in the aspx page.

  • Yes, very helpful article.  Was there an answer ever created to the question above in regard to where the processors are added to the processor list?  It seems you can add them in the web form; however they are business logic and should be stored in the service layer so that any call by a web form to a service method would use that logic.  Thanks in advance for your answer.

Page 1 of 1 (4 items)