1. Architecture basics
We use a layered design, which means that the application is separated into several layers on top of each other obeying the following rules:
- Each layer performs a well-defined function.
- A layer doesn’t know anything about layers above it, and doesn’t depend on them in any way.
- Each layer has a well-defined minimal interface that can be utilized by layers above it. The inner workings of each layer are completely hidden to other layers.
- Each layer raises the abstraction level, i.e. reduces the need for layers above it to work with layers below it by providing more convenient higher-level programming interfaces.
The following is the list of layers:
- Database – Enterprise Database hosted on Microsoft SQL Server or SQL Server Express
- EnterpriseServer – basic data manipulation, CRUD (Create-Read-Update-Delete), stored procedure calls, record locking and server-side security.
- EnterpriseWS – Web Services-based thin wrapper for EnterpriseServer that can be accessed remotely.
- EnterpriseClient – common client-side logic shared between ASP.NET and Windows applications – field auto-population, data validation, etc. Contains implementation of “Business Object” which is used by EnterpriseASPClient and EnterpriseVBClient as convenient way to manipulate data. EnterpriseClient uses EnterpriseServer interface for data access. It can communicate with EnterpriseServer either via Web Services or via direct assembly loading.
- EnterpriseASPClient – foundation of EnterpriseASP application. Provides access to EnterpriseClient functionality via DBDataSource control which resembles the behavior of standard ASP.NET SqlDataSource control as much as possible, and adds some Enterprise-specific features. EnterpriseASPClient doesn’t work directly with Web Services, EnterpriseServer or database.
- EnterpriseASP – ASP.NET application.
- EnterpriseVB – Windows application, connects directly to ASP Presentation layer.
Each of these layers is represented as a separate project in main Enterprise solution. There’s also separate EnterpriseCommon library which contains commonly used interfaces and utility classes.
During development one of our goals was to split application layout and application logic as much as possible within the business layers.
As result, the EnterpriseASP forms have almost no code , except for a few exceptions.
Under the Object, the union of all tables/relations and business logic rules needed to define the behavior of the forms chain starts from main menu. For example ObjectHeader object includes the description of things needed to define the behavior of ObjectHeaderList, ObjectHeaderDetail and ObjectDetailDetail forms.
The business logic is defined in this way:
- XML tables description (EnterpriseServer\Schema\Tables folder)
Each Object that appears in main menu should have the correspondent table description that includes the description of all tables that are used to populate the object related forms, the relations between parent/child tables, the links to lookup tables and optionally the direct SQL that will populate table data.
The format of this file is very straight forward and is designed to be self-documenting.
- Object description XML file. The format of this description is also straight forward. It includes the description of the next items:
- The link to the related table description (different objects can refer to the same table)
- Static filters that will be applied to the object list forms if needed
- Custom actions that can be applied to the object forms. As a rule the action is represented on the form as the separate or grid column button.
- Field handlers that can be used to apply some business logic to the change of some field
- Access permission for this object. You can define define the permissions for the specific user access rights that can be applied to Select, Insert, Update Or Delete object action.
- Field validators (currently only required field validators are defined, but this section can be expanded for the new custom validators too)
- Each action described in the object description must have the implementation on the level of EnterpriseClient project (all implemented actions are placed in the EnterpriseClient\Actions folder).
Actions can perform the complex operations with the entire business object. For example actions are used for orders posting, picking, shipping and so on.
- The data changes that should occur as reply to some field value change is described by field handlers. As a rule field handlers are called by client callback functions or handled during forms postbacks. The typical tasks for the field handlers - data populating when customer/vendor/inventory item is selected, warehouse bin population when warehouse is changed and so on. All implemented field handlers are placed in the EnterpriseClient\Handlers folder
- CLR stored procedures are used to apply the same business logic to object stored in the database or in the form dataset. CLR project is referenced by both server and client parts of the application, but client use only CLR functionality that is unbind from database.
The good example is the some recalculations - for example Order recalculation: all recalculations are made inside dataset, but when we want to use recalc order from client, we pass the form dataset to the recalculation method and when the recalculation method is called from stored procedure, the dataset is created by CLR procedure that pass this dataset to the recalculation method.
- Client callback functions allows to perform some operations on the form without forms reloading. They are usable when only several fields should be changed on the form, examples - order detail recalculation or warehouse bin populating. We use the specially developed CallbackControl to describe all callbacks that should be performed on the specific form. As a rule client callbacks use filed handlers to modify data.
So to program some business logic, you should not touch the form itself.
As result of this design, only about 30 of the most complex application forms (out of nearly 650) require manual modification. The Actions/Handlers can be reusable on different forms and this greatly simplifies the programming.
1.2. Server-side functionality
EnterpriseServer provides the server-side functionality such as basic database access (CRUD, Create-Read-Update-Delete), record locking, authentication, access control and specific stored procedure calls.
It also provides Translator class which encapsulates working with translation table. Another important feature provided by EnterpriseServer is access to so-called metadata, which describes links between tables (lookup and master-detail), specifies auto-population and validation rules for use with specific objects and other useful information that can be used by superincumbent layers.
The metadata is currently stored as a set of simple XML files in Schema/Tables and Schema/Objects subdirectories of EnerpriseServer projects. It’s also used internally by EnterpriseServer to generate necessary SQL commands on-the-fly. It is possible to manipulate metadata easily.
1.3. Web Services
All Web-services related functionality are completely separated from the parts of Enterprise. We designed a set of interfaces that can be used to work with database, with the main interface named IDataAccessor which provides access to all necessary objects that are required for database access, authentication, translation and other related functionality. The interfaces are defined in EnterpriseCommon assembly so that all parts of application have access to them.
There are two implementations of IDataAccessor currently: ServerDataAccessor, which is implemented in EnterpriseServer and represents the actual data access engine, and WsDataAccessor which is implemented in EnterpriseClient (WS subdirectory/namespace) and provides data access and other necessary functionality via Web Services. WsDataAccessor communicates with EnterpriseWS, which in turn uses an instance of ServerDataAccessor to perform actual work.
This way, all the code that needs to know anything about Web Services is concentrated in EnterpriseClient.WS and EnterpriseWS, and most Web Services-related issues can be dealt with by adjusting these parts only without touching any other code.
1.4. Client Support Library
EnterpriseClient provides shared infrastructure for use by the ASP.NET application. The basic primary class provided by this library is BusinessObject. BusinessObject represents a primary entity in Enterprise database, such as Order or Purchase. It consists of a DataSet and several methods to fill it and to propagate changes back to the database. The DataSet contains all tables related to the object. For example, in case of Order, these are OrderHeader (primary, or “root” table), OrderDetail, plus lookup tables – CustomerInformation, CurrencyTypes and so on.
Another important function of BusinessObject is handling of field auto-population which it delegates to another EnterpriseClient class, DataEventManager, and validation. Instances of BusinessObject can be obtained via static (“shared”) method GetBusinessObject of DB class. BusinessObject is designed to be “long-lived” together with its DataSet. It’s stored in Session state in ASP.NET application (inside DataEditState objects, see the section on ASP.NET architecture) and will be stored in every Form class in the Windows application.
The GetBusinessObject method of DB class accepts as arguments the name of requested object, a set of session filters, a set form filters, object type and record mode.
The name of requested object is the name of database entity such as OrderHeader, InvoiceHeader or CustomerInformation. Session filters are CompanyID / DivisionID / DepartmentID, packaged for convenience into specialized FilterSet container. Form filters are represented by another FilterSet containing the key for requested object, e.g. OrderNumber; it’s used only in case if an existing object is requested. Object type tells whether List or Detail object should be requested. Record mode tells whether a new object should be created or existing one should be returned. In case of new object, it’s not immediately created in database; database records will be created after Update method is called for the BusinessObject.
After a BusinessObject is obtained, application can perform all necessary modifications on its DataSet and then send these modifications back to database using Update method. A DataEventManager instance which is loaded automatically inside BusinessObject will react on field changes as necessary, populating CurrencyExchangeRate upon change of CurrencyID and performing other actions that are need for the form to function properly. Having said that, the most actual work with BusinessObject is performed not directly by specific forms, but rather via EnterpriseASPClient layers.
The important feature provided by separating the EnterpriseClient layer is extended possibility for business logic tests. Testing can be performed by interacting with EnterpriseClientlayer directly without any need to work with user interface. A test recorder can be devised easily by placing a simple adapter between EnterpriseClient and EnterpriseASPClient which will intercept all DataSet operations performed and record them as XML file which later can be replayed by calling working with BusinessObjects directly. Moreover, user interface can be tested for correctness by providing fake EnterpriseClient interfaces to EnterpriseASPClient layers which will check how well field values and action requests propagate from user interface to BusinessObject.
Besides BusinessObject infrastructure, as it was already said, EnterpriseClient contains code necessary to communicate with EnterpriseServer either via Web Services or direct assembly loading. But it should be noted that most of code even in EnterpriseClient itself is agnostic concerning concrete access method, save EnterpriseClient.WS and a short private method in DB class which initializes DB.DataAccessor with concrete accessor instance. The layers above EnterpriseClient don’t have even slightest idea on whether Web Services-based or direct access method is used.
1.5. ASP.NET Support library.
EnterpriseASPClient library provides access to EnterpriseClient / BusinessObject functionality via standard ASP.NET DataSource-based data access model. It provides DBDataSource control that mimics the standard SqlDataSource. DBDataSource must be provided with information on which object should be used, is it list or detail and which table is primary for the form the DBDataSource instance belongs to. Controls in FormViews / DetailsViews can be bound to that DBDataSource. Accept() method of DBDataSource can be called to save changes and close current form (e.g. upon click on “Ok” button), and Reject() method can be called to reject these changes (e.g. upon click on “Cancel” button).
DBDataSource uses another class in EnterpriseClient, DataEditState, which maintains active BusinessObjects and active form tree which is used for navigation. There’s a single DataEditState for each database record currently open, be it OrderHeader, OrderDetail or CustomerInformation. Single BusinessObject can be shared between several forms / DataEditStates, e.g. OrderHeader record and OrderDetail records for the same order share same BusinessObject which is stored inside DataEditState corresponding to OrderHeader table.
In addition to DBDataSource, EnterpriseASPClient contains also other useful supporting infrastructure. It provides common base class for all EnterpriseASP pages, PageBase. This base class is used to support translation and client callbacks.
Client callbacks are used to minimize amount of page refreshes while in EnterpriseASP.
EnterpriseASPClient.ASP.NET page translations are implemented using PageTranslator class which uses new ASP.NET feature called “Custom Expressions”. With this feature, one may write
<asp:Label ID='lbPageHeader' runat='server' Text='<%$ Translation:Order Header %>'></asp:Label>
so that the Text of the Label gets translated automatically, or “Order Header” is used if no matching translation is found. The Custom Expression-related machinery resides in TranslationExpressionBuilder class.
In addition to features described above, EnterpriseASPClient also provides several controls for use in EnterpriseASP, including PopupControl, navigation controls and other useful components.
1. Architecture Features
- New Integral Accounting Enterprise Application Framework that supports all of the existing client forms/server functionality.
- The system uses a new approach to the data access, based on the xml objects description.
- The main Login Form is now based on the EntepriseMembershipProvider class
- New TreeView controls nested within panels used for navigation
- Visual Studio Master pages & Themes support
- New Language Translation Engine. Supports any number of languages, translations done on the fly, can select languages in real-time, user now has the ability to modify the translation or add their own language, or edit the provided translations.
- Greatly reduced number of postbacks to the client and eliminate a lot of the un-necessary form refreshes. The Client callback function support that prevents page flicker (used for data population after some control contents changing)
- Action support based on the xml action description. (All form actions like Book Order, Memorize and such are described in inside object xml file and implemented automatically based on this description)
- Advanced SQL Server Database Support, for example the sharing of codes between stored procedures and the application, order detail recalculation being a good example of this implementation.
- New general/user role security implementation
- New web services, much fewer, much smaller, easier to customize and modify.
- Search/paging/sorting support for the grids.
- Assigning new auto-generated numbers to the documents
- New transactional model - no order is posted to the database when you create the order detail items for new order, order is posted to the database together with all child records during order saving only.