7
votes

I've been researching this for weeks. I'm currently designing a loosely-coupled architecture design using n-tier (3-layered) method and factory design approach. My goal is to put each client's business logic (ClientA.DLL, ClientB.DLL) in separate namespaces so that the project scales out, meaning I can modify/remove/add a specific client's business logic without affecting the others, because they're not dependent on each other. Then I invoke the client's namespaces/class using the client's unique identifier (a string value that is maintained in the database) via the Factory namespace. The Factory.DLL also hides the per-client logic, while the BusinessAbstract.DLL serves as the Layout or the Template that the per-client's classes will be using.

Here is the project solution:

alt text

And here is the actual code:

BusinessAbstract.DLL

namespace BusinessAbstract
{
   // the entity / data transfer object
   public class MemberDTO
   {
      public string MemberID { get; set; }
      public string MemberName { get; set; }
    }

   // the interface
   public interface IMaintainable
   {
      void Add();
      void Edit();
      void Delete();
   }

  // the base abstract class, implements the Entity and the Interface
  public abstract class Member : MemberDTO, IMaintainable
  {
    // Implement IMaintanable but change it to abstract
    public abstract void Add();
    public abstract void Edit();
    public abstract void Delete();

    // a method with Database access, get from DAL
    public virtual MemberDTO GetMemberDetails(params object[] args)
    {
        return DAL.MemberDAL.FetchMemberDetails(args);
    }

    public virtual string GetClientBLL()
    {
        return "base's method";
    }
   }
 }

ClientA implementation of the AbstractBusinessRule

ClientA.DLL

 namespace ClientA
 {
    public class _Member : BusinessAbstract.Member
   {
       public override void Add()
      {
        throw new NotImplementedException();
      }

      public override void Edit()
      {
        throw new NotImplementedException();
      }

      public override void Delete()
      {
        throw new NotImplementedException();
      }

      public override string GetClientBLL()
      {
        return "ClientA Method";
      }
    }
 }

The Factory

Factory.DLL

 public static class Invoker
 { 
     public static T GetMemberInstance<T>(string clientCode)
        where T : Member, IMaintainable
      {
        Type objType = Type.GetType(clientCode + "._Member," + clientCode);
        return (T)Activator.CreateInstance(objType);
      } 
  }

Sample implementation on Presentation Tier

the Website

 protected void Page_Load(object sender, EventArgs e)
 {

    // invoke Member class using String hardcode
    Member obj = Invoker.GetMemberInstance<Member>("ClientA");
    Response.Write(obj.GetClientBLL()); //prints clientA method

    obj = Invoker.GetMemberInstance<Member>("ClientB");
    Response.Write(obj.GetClientBLL()); //prints clientB method

 }

And you'll also notice that I have a DAL folder in each of the client DLLs as well as the AbstractBusinessRule DLL, because I also want to scale the DAL layer and use the layer structure "UI-BLL-DAL."

Any comments/suggestion about this design are welcome. I'm hoping for input on how I can improve this structure. Thanks in advance.

4
Can anyone tell me if domain object is more or less common a term than DTO? I just ask because I've never heard of DTO before, but most people know what I mean when I say domain object, and I think that's what the DTO is..Jimmy Hoffa
Yes I think it's just an old-school name for the Domain/Business object or entity. Value Object that it is..CSharpNoob
DTO == data transfer object, but I don't think the OP is using it that way at all. Specially considering the DTOs usually don't do much than holding data to be passed around, so its not like you put business logic in there or initiate persistence operations from it.eglasius
I just attached it to the real business object, as you can see the business object (Member) besides holding operations, can also pass data around the layers because of the MemberDTO it inherited. , I would say its kinda unorthodox approach though ^^CSharpNoob
A DTO is often used as a pass-through between layers; an example would be the ViewModel that a View presents (or passes back to the Model) in MVVM. It's a bit-bucket representation of some subset of the data in the model, which is used for some other purpose.Matt Mills

4 Answers

1
votes

The only thing I see, and I mist just be missing this in looking at your post, but I don't see a DAL interface definition or abstraction layer that seperates it from your BL in the way your BL is abstracted from your presentation.

This is important because it gives you the flexibility in the future to create a new business layer using the same data without having to rewrite the DAL, or replacing your database with flat CSV files/mocks in unit testing/a 3rd party maintained soap web service response, or whatever else might be a better data storage mechanism in the future.

0
votes

You've got a basic violation of Separation of Concerns/Single Responsibility Principle: Your business objects know about their storage.

The Data Layer of the 3-tier architecture should be responsible for CRUD operations, and should be queried for instances of the objects consumers need. Something like this:

Presentation Layer ------- Data Layer
                              ||
                              ||
                       Business Layer

This allows the business layer to focus on implementing, and keeps the persistence concerns out of it. If the Presentation layer needs a new business object (for creation), it asks the data layer for it.

0
votes

My first comment would be that your names need to be much more descriptive. It's not obvious at all what your program actually does by looking at the solution outline. If you give your client classes and your workspace meaningful names it'll be a step in the right direction.

0
votes

this question came out too broad, there is not a single best for all approach.

Personally basing extension points in adding classes and inheritance without a real case that strongly benefits from it is something I've seen ending in great complexity all around.

Its very hard to tell with the amount of info provided, but consider the alternative of using a more configuration based approach / don't mean config files, just configurations that are passed into the system.

You can have a set of basic rules, and a default configuration that applies a set of those rules to a client. If you are doing it in code, when adding a client's config you may just say .AddClient("ClientA"), and it'll use only default rules.

Alternatively you can specify the rules that apply to the client's processes when adding the client, which can involve setting different rules or even different configuration values for those.

ClientA can have a need that's not included in the basic rules, then a custom/code business rule can be applied to the client process.

I wouldn't try to make the jump all the way to such a generic framework. Instead I'd keep the above focused on the specific processes and exposing extension points for those. As you work in the solution, common patterns should emerge and then if appropriate (a real benefit is seen) you can refactor it to something more general.