简体   繁体   中英

.NET Application Architecture - Which is the optimal assembly for this class?

I have a .net web application that I have taken over, that had in the past suffered from all business logic being in code behind pages and very much tied to the UI.

I have spent some time refactoring, in particular moving data access code into a dedicated data access project "Company.DataAccess" (for example). Other logical portions of code also have their own assemblies too.

What I'm not comfortable with, is the placement of objects that need to be shared across assemblies:

For example - In my project "Company.ClientDataImport", I have classes containing the business logic for the import of client data.

One particular piece of functionality that comprises this code, is that the client import format can be mapped to our default import format. So, I have a class "DataImportFieldMappingInfo" within the "Company.ClientDataImport" assembly:

public class DataImportFieldMappingInfo {

    int CompanyFieldName
    {
        get;
        set;
    }

    int ClientFieldName
    {
        get;
        set;
    }
}

These mappings are stored in a database, so at some point, I need to populate a collection with instances of this object.

One architecture aim, is that all database IO should be handled by "Company.DataAccess".

I want "Company.DataAccess" to be aware of the class DataImportFieldMappingInfo so that it can utilise the class as was intended for storing this type of data, but this means that "Company.DataAccess" and "Company.ClientDataImport" both need to be aware of DataImportFieldMappingInfo so that they can communicate using the same classes.

My solution to this common problem so far, is to use another library called "Company.DomainEntities" that contains classes for objects that are shared across other assemblies in the application. This works well, and all assemblies can communicate in terms of the same objects. The classes really just contain properties, so they are just data containers as opposed to containing application logic.

What I don't like about this is that DataImportFieldMappingInfo is a data import concern, and so I believe it should be in that namespace, and therefore in the "Company.ClientDataImport" project. Circular reference restrictions prevent this however, so I have to use the "Company.DomainEntities" project as a middle man.

Is there something wrong with my architecture, or is this common practice in this sort of situation?

In that scenario, breaking the shared types / interfaces out into a separate assembly is fairly normal.

Another approach, not unheard of, is to simply say "all these assemblies aren't doing anything extra", and just use the namespaces to compartmentalise the code. If you don't need to be able to run with only a subset of the dlls, why not just have one dll? The classic counterpoint to this is where you have multiple different UIs / services all consuming the same dll (or different subsets of the dlls).

I tend to agree with Marc; but there's a couple of other things you might consider:

What constitutes as "Business Logic" vs "Data Access" code vs simple data structures which you app "knows" about? At first glance DataImportFieldMappingInfo looks like it might be Data Access related but I think they are actually just common structures (and are more business orientated) so putting them in a common assembly would make sense - assuming you use them to exchange data between different layers / assembilies.

The other view to take is that all data repositories (which includes databases specific to your application as well as all external data sources like external databases, files or systems) are treated equally and accessed through an interface - not through a single concrete implementation. In this case all data is specified in a Business Logic / Domain centered way and not a Data Access / Repository specific way. In this case you'd also define all the common data structures used by your app in a common assembly.

Edit:

In short, interfaces allow you to abstract out complexity and dependencies associated with nasty stuff which you'd rather not be closely (and permanently) associated with.

The principle behind using interfaces is Dependency Inversion (DI). (There's no shortage of information about this - in fact you might find there's too much). DI goes along way to helping build systems that play nice with the Stable Dependencies Principle .

The idea is that rather than having your Business Logic (assembly/s) depend on the Data Access, you have them both depend on the interface; you can then swap out implementations anytime you want. You might have a "real" Data Access component that works against SQL and a "dummy" one that returns dummy test data straight from your code.

When you design the interface the idea is to design it from a Business Logic / Domain perspective - not a technical one; although there are cases where that's appropriate. The job of the implementation (ie: the Data Access class that implements the interface) is to translate between the Business orientated interface and whatever strange data source it's dealing with. For example, your average SQL based data provider might suck out data via a DataReader and convert in into a MyBusiness.Common.Info.OutstandingAccountsCollection .

Code Structure

  • Common Assembly (MyBusiness.Common) - contains any data structures that are used to exchange data between your layers (eg: the MyBusiness.Common.Info.OutstandingAccount and MyBusiness.Common.Info.OutstandingAccountCollection classes).
  • Interface Assemblies: you might have several of these. If all your data access is via the one system (say SQL) then you'd probably have just the one (MyBusiness.Interfaces.DataAccessProvider), but if you have various system to interface with you'd want to keep them seperate. MyBusiness.Interfaces.DataAccessProvider references the Common assembly because it will include the 'info' types in it's contracts.
  • Concrete Data Access assembly(s): (MyBusiness.DataAccess.SQLDataAccessProvider). this references the Common and Interface assemblies.
  • Your Business Logic (MyBusiness.BusinessLogic) references the Common and Interface assemblies.

For more info (and diagrams) check out this piece (self promotion alert): 5-Layer Architecture

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM