| How do I get started implementing your suggestions from Part II? |
| |
| The following is a small list of standard assemblies that you'd need for most applications. The naming convention is not important. It includes instructions on the types of classes, methods, properties, and events that should be included in each assembly (dll/exe). For the sake of this discussion, we'll call our application family ProductA. This is not a one size fits all solution. However, it does give you a good framework to begin with and then modify depending on complexity. |
| |
| The Foundation |
| |
| 1. | ProductA.DataClasses.dll |
| (References: nothing) |
| Contains all of your data classes and any interfaces associated with them. These classes have no methods related to database access and no properties related to specific database providers. |
| Almost all of the other assemblies in our application will have a reference to ProductA.DataClasses.dll. So, it cannot contain items that are dependent on assemblies that are dependent on ProductA.DataClasses.dll or you'll run into circular reference issues. |
| As a general rule of thumb, all of the classes, methods, events, and properties in this assembly should be exposed as public. |
| |
| 2. | ProductA.Shared.dll |
| (References: ProductA.DataClasses.dll) |
| Includes classes that can be used in any environment including the desktop, a web server, or even a mobile device via the .NET Compact Framework. Items such as enums, generic utility methods, generic data processing methods (web requests, file reads/writes, natural language translations, etc...) Only code that can work in every environment can be placed here. This includes any configuration file management methods. The .NET Compact Framework may or may not have the built-in classes that other .NET environments do. |
| If you have built generic non-environment specific utility libraries that can be used across applications, there is nothing wrong with adding a reference to them in ProductA.Shared.dll |
| Almost all of the other assemblies in our application will have a reference to ProductA.Shared.dll. So, it cannot contain items that are dependent on assemblies that are dependent on ProductA.Shared.dll or you'll run into circular reference issues. |
| As a general rule of thumb, all of the classes and properties in this assembly should be exposed as public. |
| |
| The DataBase Layer |
| |
| 1. | ProductA.DataBase.DataAccess.dll |
| (References: nothing) |
| Contains all of the provider specific data access calls. For calls that return results, provides at least a minimum set of method options to return a DataTable or a DataSet. You'll want to use Code Access Security to prevent other layers from calling these methods directly. You'll want to enforce that they do so via your primary database layer interface defined in ProductA.DataBase.dll. |
| If you don't want to mess with Code Access Security, you could incorporate ProductA.DataBase.DataAccess.dll classes in the same assembly as ProductA.DataBase.dll. Just set the classes in ProductA.DataBase.DataAccess to be internal. In the event that somewhere down the road you need to break these up for implementing another database type, you can do so without disrupting layers above the data access layer. |
| As long as you maintain the same public interface, you could swap out assemblies here depending on the database your product is using. You could also implement a Factory Pattern for database providers all in the same assembly. You need to remember though that each database product offers different features. If you are going to enable your application to use different databases, you need to be careful how you utilize proprietary features of each database product. |
| |
| 2. | ProductA.DataBase.dll |
| (References: ProductA.Shared.dll, ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll) |
| This assembly serves as the public interface of your database layer. Do not use database provider specific objects in this layer. The classes in this layer should call the corresponding method in the ProductA.DataBase.DataAccess layer, and where applicable, return populated classes, class arrays, datasets, or datatables to other logic layers. |
| ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll, and ProductA.DataBase.dll require a fair amount of work to create. If your database is either Microsoft Access or SQL Server 2000, then you'll want to download the source code for the ADO.NET Code Generator. This tool can open a database, review its tables and stored procedures and then generator code for all three of these assemblies. Just click the button and let it do the work. It will even construct code for ProductA.DataBase.dll to populate your classes at runtime. No hand coding of column name in the result set to class property name. |
| Some object oriented purists would tell you that the code in ProductA.DataBase.dll is actually part of the business layer. They believe populating data classes with results returned from the database should be viewed as "business logic". I happen to disagree largely because I've automated this task via the code generator mentioned above. To me, the "business logic" layer should require a programmer to write special code to process custom rules against data. That said, you could easily move the classes in ProductA.DataBase.dll into the low level business logic area discussed below. |
| In most cases, I like to have all of my data access layer methods set as static (Shared in VB.NET) or implemented via a singleton pattern. There is no real reason to ask the framework to load multiple instances of these at runtime. Plus, it makes it easy for user interface and business logic layer developers to discover which methods they can call via Visual Studio .NET intellisense. |
|
|
| The Business Rule Layer |
| |
| The business rule layer is the glue between the user interface layer and the database layer. It is often the most difficult layer to design. As a general rule, you'll want to break this up into at least two different areas: high level and low level. |
| The high level area is dependent upon the environment of the user interface and is exposed publicly to the user interface. It will need to have knowledge of or references to user interface controls. Its usefullness really shows itself when you have multiple applications running in the same operating system environment which share the same data access layer. This area has the highest likelihood for code duplication because the user interface controls are different for each environment. |
| For example, if you have a method that should iterate through a grid and save data, the grid control in an ASP.NET application isn't suitable for use in a .NET Windows Forms application that uses a windows DataGrid in a similar UI process. As you are writing these types of methods, look for ways to push non-environment specific logic down to the low level area to reduce the total amount of duplication. It is ok to call database access layer methods directly from the high level area. |
| The low level area (ProductA.BusinessLogic.dll below) should not have any knowledge of or dependencies on user interface controls or objects. Anytime a high level area method needs to call a low level area method, it should pass control values as standard .NET object types as parameter values. For example, you wouldn't pass a ListView.Item as a parameter to a low level area method. You'd pass the value of the ListView.Item as a string, int, double etc... instead. The low level area is where "most" database access layer calls will be initiated from. You need to make a decision on a class by class and method by method basis as to whether you want them exposed to the user interface layer. Typically, you'll want to hide everything from that layer that it doesn't need to see or that you don't want it to call directly. This reduces confusion for the user interface developers. |
| In most cases, I like to have most of my business layer methods set as static (Shared in VB.NET) or implemented via a singleton pattern. There is no real reason ask the framework to load multiple instances of these at runtime. If you have methods and classes that get used rarely, then not setting them as static is fine and may be preferrable. |
| |
| 1. | ProductA.BusinessLogic.dll |
| (References: ProductA.DataClasses.dll,ProductA.Shared.dll,ProductA.DataBase.DataAccess.dll,ProductA.DataBase.dll) |
| Low level business logic area. |
| |
| 2. | ProductA.Desktop.dll (Used with a .NET Windows Forms Application - if applicable) |
| (References: ProductA.Shared.dll,ProductA.BusinessLogic.dll, ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll, ProductA.DataBase.dll) |
| High level business logic area. |
| |
| 3. | ProductA.DesktopMobile.dll (Used with a .NET Compact Framework Application - if applicable) |
| (References: ProductA.Shared.dll,ProductA.BusinessLogic.dll, ProductA.DataClasses.dll,ProductA.DataBase.DataAccess.dll, ProductA.DataBase.dll) |
| High level business logic area. |
| |
| 4. | ProductA.Web.dll (Used with a ASP.NET Application - if applicable) |
| (References: ProductA.Shared.dll,ProductA.BusinessLogic.dll, ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll, ProductA.DataBase.dll) |
| High level business logic area. |
| One item of interest here. I work a lot with software families that share analytical data back forth between the desktop and the web. In some instances, we even replicate what data would look like if it existed on the web via our desktop applications. Eliminates the need for desktop users to send data up to the web, store it, then log onto the web site and view the data in order to ensure it gets rendered correctly. If you do this sort of thing, you'll want to consider adding methods in this layer to render pieces of pages and return the results either to the .aspx code behind class or send the rendered portion back to the desktop over .NET Web Services. The point is, give a lot of thought as to where you put your code before writing the first line even for things as simple as rendereing an html table. |
| |
| The UI Layer |
| |
| No business logic can reside in form events or methods. No database provider code can reside here either. The forms and classes that reside in this layer must be strictly devoted to capturing input from users and displaying results or progress. Only a small percentage of application wide source code should reside in this layer. If you live by the notion of "can this logic reside in the business layer", it probably should reside there. |
| It is ok to call database access layer methods directly from the user interface layer and bypass the business logic layer entirely if no additional processing or tweaking of the results is required. |
| |
| 1. | ProductA.exe (.NET Windows Forms Application - if applicable) |
| (References: ProductA.Shared.dll, ProductA.BusinessLogic.dll, ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll, ProductA.DataBase.dll, ProductA.Desktop.dll) |
| |
| 2. | ProductA.exe (.NET Compact Framework Application - if applicable) |
| (References: ProductA.Shared.dll, ProductA.BusinessLogic.dll, ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll, ProductA.DataBase.dll, ProductA.DesktopMobile.dll) |
| |
| 3. | ProductA.com (ASP.NET Application - if applicable) |
| (References: ProductA.Shared.dll, ProductA.BusinessLogic.dll, ProductA.DataClasses.dll, ProductA.DataBase.DataAccess.dll, ProductA.DataBase.dll, ProductA.Web.dll) |
| |
| That concludes the overview of the basic set of assemblies you'll need for a flexible design. Individual applications will likely require additional assemblies for things such as importing and exporting data. When you add on these additional functionalities, give careful thought as to how they might need to be reused in related applications. |
| |
| Frequently Asked Questions |
| |
| 1. | How do I know to define my classes, methods, properties, and events as public, internal, protected, static, etc... ? |
| A Brief Synopsis of C# Class and Method Modifiers |
| |
| 2. | How do implement progress bar tracking without violating design guidelines across all my application layers? |
| .NET Asynchronous Events To Send Process Status To User Interface |
| |
| 3. | What is a Factory Pattern? |
| There are few different ways to implement a Factory Pattern. The idea being that can pass a parameter to the factory and dynamically return a specific class back based on that parameter. Here's one example: How To Use Refactoring To Handle Multiple Versions of Classes. |
| |
| |
| 4. | What is a Singleton Pattern? |
| Exploring the Singleton Pattern |
| |
| 5. | What is Object Oriented Programming? |
| Object Oriented Programming - Basic Concepts |
| |
| 6. | What was the data access layer code generator mentioned earlier? |
| ADO.NET Source Code Generator |
| |
| 7. | Can you give me an example of how a business process might change and how to prepare for it? |
| Managing Database Driven Application Permissions In SQL Server |
| |
| 8. | What other tutorials do you have that might help me? |
| Complete List of Articles & Complete List of Tips |
| |
| 9. | Do you have a code sample to download? |
| Download Basic Code Sample |
| |
| 10. | How can I use Generics (.NET 2.0 Feature) to create better designs? |
| Generics and Clueless Business Layers |
| |
| |
| Part I: Basic assumptions you should make with every application. |
| Part II: 6 basic rules you can follow to help address the assumptions from Part I |
| |