Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Introduction
Developers must realize there is more to programming than simple code. This two-part series addresses the important issue of
application architecture using an N-tier approach. The first part is a brief introduction to the theoretical aspects, including the
understanding of certain basic concepts. The second part shows how to create a flexible and reusable application for distribution to any
number of client interfaces. Technologies used consist of .NET Beta 2 (including C#, .NET Web Services, symmetric encryption), Visual
Basic 6, the Microsoft SOAP Toolkit V2 SP2, and basic interoperability [ability to communicate with each other] between Web Services
in .NET and the Microsoft SOAP Toolkit. None of these discussions (unless otherwise indicated) specify anything to do with the physical
location of each layer. They often are on separate physical machines, but can be isolated to a single machine.
For starters, this article uses the terms "tier" and "layer" synonymously. In the term "N-tier," "N" implies any number, like 2-tier, or 4-tier,
basically any number of distinct tiers used in your architecture.
"Tier" can be defined as "one of two or more rows, levels, or ranks arranged one above another. So from this, we get an adapted
definition of the understanding of what N-tier means and how it relates to our application architecture: "Any number of levels arranged
above another, each serving distinct and separate tasks." To gain a better understanding of what is meant, let's take a look at a typical N-
tier model (see Figure 1.1).
We must gain a very basic understanding of Object-Oriented Programming (OOP) at this time. To clarify, let's look at another example,
such as a shopping cart application. Think in terms of basic objects. We create an object to represent each product for sale. This Product
object has the standard property getters and setters: getSize, getColor, setSize, setColor, etc. It is a super simple implementation of any
generic product. Internally, it ONLY knows how to return information (getters) and understands how it can validate the data you pump into
it (ONLY for its limited use). It is self-contained (encapsulation). The key here is to encapsulate all the logic related to the generic product
within this object. If you ask it to "getPrice," it will return the price of the single item it represents. Also if you instruct it to "validate" or
"save," it has the brains to be able to handle this, return any errors, etc.
We can plug this Product object into another object, a "Cart" object. This cart can contain and handle many Product objects. It also has
getters and setters, but obviously on a more global scale. You can do something like "for each product in myCart", and enumerate (loop
through) each product within. Now, when you call "getPrice" for the Cart object, it knows that it must enumerate each product that it has,
add up the price for each, and return that single total. When we fire the "saveCart" method, it will loop for each "product" and call its
"saveProduct" method, which will then hit the Data Access Tier objects and methods to persist itself over to the Data Tier.
We could also take our simple Product object, and plug it into our "Sale" object. This Sale object contains all of the items that are
available for a particular sale. And the Sale object can be used for things like representing all the items on sale at a given outlet or the
like. I'm sure you are beginning to understand the advantage of using an OOP environment.
Conclusions
In all of the systems that I have been able to dig my dirty little hands into, I have rarely ever seen both the Business Tier and Data Access
Tiers used. I mostly combine the two tiers. Allow the Business Layer to talk directly to the Data Layer, and do not bother with the Data
Access Layer. To justify this, we are all developing on Internet time, and the last time I looked, it's still going at about 3 to 4 times faster
than normal time, which means we are expected to also work and produce at the same rate. The bottom line is reducing the time to
market. In my opinion, writing this Data Access Tier, which is simply abstracting the Data Tier, is overkill, and ADO can be considered as
this Data Access Layer. It provides us with the interface directly. We still keep all SQL in the Data Tier (stored procedures), but no
business rules should be kept here.
Of course, the more tiers you add, the more performance is affected. The client hits "Save Cart" on their Web-enabled phone, it hits the
Business Tier to call the "Cart" "saveCart," which calls the products "save," which goes either directly to the database or goes through the
Data Access Layer and finally persists into the database. This path does affect performance. It is up to the application architect (you) to
know and understand this, and all other factors affecting the system, and be able to make a good decision on how to develop it at this
level. This decision is usually pretty easily made, depending on the amount of work and documentation that was produced from the
analysis phase.
We all now know how to do this logically. Let's explain the why. A good example is to look at the Presentation Logic Tier. Notice that
many of its sections --the Web, the Proxy Tier, and the Client Interface -- all sit directly on top of the Business Tier. We gain the
advantage of not needing to redo any code from that Business Tier all the way to the database. Write it once, and plug into it from
anywhere.
Now say you're using SQL Server and you don't want to pay Microsoft's prices anymore, and you decide to pay Oracle's instead. So, with
this approach you could easily port the Data Layer over to the new DBMS and touch up some of the code in the Data Access Layer to use
the new system. This should be a very minimal touch-up. The whole point is to allow you to plug each layer in and out (very modular)
without too many hassles and without limiting the technology used at each tier.
Another example would be that we initially develop our entire system using VB (COM) and ASP, and now we want to push it over to our
friendly VB .NET or C#. It is just a matter of porting the code over at each layer (phased approach), and voila, it's done. (Microsoft has
given us the ability for interop between classic COM and .NET.) We can upgrade each layer separately (with minor hurdles) on an as-
needed basis.
Introduction
The purpose of this article is to take the theoretical explanation given in Part 1, and show you how to apply it to your future applications.
The example I have chosen is a fairly simple one, an address book. I will walk you through the development and integration of an N-tier
model, showing you how it can be easy to implement a new Database Management System (DBMS) (unplug your current database and
plug in a new one), and finally how to take your existing middle tier and deploy it on to a new platform (Web Services). My platform of
choice is the .NET Framework Beta 2, using Visual Studio Release Candidate 1 (RC1), using the new .NET language, C#. It is important
for you to not focus on the actual code of this article. That is irrelevant. Instead, put more focus on the way in which I developed and
separated each tier of the application. You must also realize that I'm not going to formally walk you through any distinct stages of an
application systems development life cycle, just the bare bones needed for the completion of this application. I will also not cover
extensive error-handling routines, and security is always a major issue with any application so I have put in place only the minimum
needed for this article. Please consult one of the many resources regarding these features and implement them at your leisure.
The rest of this article will work down from our Data Tier to the Presentation tier, in that order.
Think of the Data Tier as your DBMS (a.k.a. Access or SQL Server). We must create our database so that it will solve our requirements
listed above and at the same time not limit our future development. Usually the database administrator does this work, but since this is a
fairly small application, we will assume that role and throw the database together.
We start by opening up Access and creating a blank database. I called it "addressbook.mdb." Let's begin with the Contact List that will
represent your standard user. I decided to use the Email Address as our Primary Key1 (PK) (explanation footnoted at end of section). (I
have included the Access database as a part of the download for this article).
Figure 2.1 Contacts Table
zero
Key name type size defaultvalue required length indexed
PK emailaddress text 150 yes no yes
yes,
password text 10 no yes duplicates ok
first text 50 no yes no
last text 50 no yes no
areacode Number Integer 0 no no
Long
phone Number Integer 0 no no
Long
fax Number Integer 0 no no
address text 200 no yes no
address1 text 200 no yes no
city text 50 no yes no
zip text 10 no yes no
state text 75 no yes no
country text 50 no yes no
The second table we will throw together is the Address Book table. This is going to represent each individual address book in the system.
zero
Key name type size defaultvalue required length indexed
Long yes, no
PK bookid autonumber Integer yes no duplicates
yes,
name text 50 yes no duplicates ok
description text 250 no yes no
If we take a look at both of these tables, you will see that the there is no relationship between them. What we must do is define what is
called an "intersection table." This is simply a table that will contain both of the PKs from each table, which is used to relate each table to
each other. We use this "intersection" to assign a contact to an address book, for example, and other things.
zero
Key name type size defaultvalue required length indexed
FK Long yes, duplicates
(PK) bookid number Integer 0 yes ok
FK yes, duplicates
(PK) emailaddress text 150 yes no ok
relationship Number byte 0 yes no
Let me run you through each of the fields for this table. The "bookid" field is the PK from the AddressBook table, represented in this table
as a Foreign Key1 (FK). The "emailaddress" field is the PK from the Contacts table and is also represented in this table as a FK.
Together, these two make up the PK for this table. This allows us to have any number of contacts belonging to any address book. It also
allows us to not allow a contact to appear more than once in a given address book. The last field,"relationship", is used to indicate the
relationship between these records. Let's create a new table to describe these relationships. Figure 2.3.1 Relationship Table
zero
Key name type size defaultvalue required length indexed
PK id AutoNumber Long yes, no
Integer duplicates
name text 100 yes no no
description text 255 no yes no
You may be asking yourself what exactly is a relationship and why do we need it? A relationship, in this case, is how we relate the specific
contact to the address book. An example would be "Owner", or "Administrator", or "Contact". Here is some sample data for this table:
id name description
1Owner An owner of this address book
2Contact A contact belonging to this address book
3Administrator An administrator of the address book. These users can edit, add,
and remove contacts of the book, but cannot delete the entire
book itself.
To the question "Why do we need this?", I say take a look back at our requirements, specifically A3. It states "An Address Book has to
have one owner, but can be owned and administrated by many." This will allow us to enable the system to have any address book with a
designated owner, but also an administrator who would have some of the same abilities as the owner. This introduces a model where we
can grant access to different users to perform different actions to our address books and contacts.All we need is to have at least one
owner defined for each address book, and the rest can be optionally used in order to start associating contacts to each address book.
This will also allow us to define more roles in the system and allow for contacts to have different sets of access to any given address
book. I have found this to be a very flexible model.
1 Keys -- primary and foreign -- They are used in establishing relationships between records. The primary key in a table is the data field
in each record, which is guaranteed to be unique and which can therefore be used to establish relationships between each record in that
table and other records in other tables. The foreign key in a table is a primary key for records in another table. Foreign key values don't
have to be unique within a table, thus allowing a one- (primary key) -to-many (foreign key) relationship to exist.
Let's start the Visual Studio.NET development environment and create a new ASP.NET application. I chose to start out with an ASP.NET
application because our primary interface for this application will be through the Web and ASP.NET. In the Visual Studio.NET start page,
we first hit the "New Project" (1) button. Personally, I prefer using C# rather than Visual Basic so I chose "Visual C# Projects (2), and
using a "ASP.NET Web Application" (3), I created the application at the location http://localhost/AddressBook (4).
The next application we will add to our solution will be used to contain the Data Access tier. In the Solution Explorer, right click the
"AddressBook" Solution, choose "Add", "New Project". This new project will be a reusable C# (1), Class Library (2), with the name
"DataAccess" (3).
Change the name of the Class File Name. This can be found in the Solution Explorer, Click the "Class1.cs" file, and view the Properties
pane. You will see the "File Name" attribute. Rename it "AddressBookDA.cs".
The next application we will add is our Business tier. Follow the same procedure as we did to add the Data Access tier, but give it the
name "Business" (3). Don't forget to change the name of its default class to "AddressBookBiz.cs". And finally, since we wish to expose
this entire application over Web Services (HTTP), we will also add in a C# ASP.NET Web Service (3) application. Make sure you change
its location to http://localhost/AddressBookService. This time rename the "Service1.asmx" file to "AddressBook.asmx".
The next step will be to set up the dependencies for each application. the dependencies will become pretty clear. The Business tier will
depend on the Data Access tier, and above that the Presentation tier (ASP.NET application, and Web Services application) will depend
on the Business tier.
Right click on the "Business" application, and choose "Add Reference", "Projects" tab, and then select the "DataAccess" application. Hit
the "Select" button to add it to the "Select Components" section. Repeat this process for both the "Address Book" application and the
"AddressBookService" application, but for each of these, choose the "Business" project. The next few steps will guide you through the
setup for each application.
Most of this code is standard. To reiterate, the most important thing to remember for this tier is that it is a set of specific methods that are
used to perform all of the database interactions. It must NOT contain any business logic or presentation items. If you wish, take a look in
the AddressBookDA.cs file (part of download accompanying this article) to see what methods I exposed and how I perform the database
operations with it now. Also pay close attention to how this has been created for an Access database. Since each DBMS we use in our
careers has different capabilities, our Data Access tier will differ slightly for each. One of the nicest features of an N-tier architecture is the
ability to easily switch this layer out when a new DBMS is put in place. I will develop this application based on an Access database. When
the time comes to upgrade to SQL Server, or another quality DBMS, we will only need to modify our Data Access tier and the rest of the
application will function unchanged. This means no more rooting though thousands of lines of code just to upgrade our in-line SQL to
stored procedures.
In our Business tier we must expose a set of methods that will be useful and that will solve the business rules. It is also important to
remember that these objects can be stateful for the lifetime of that specific object instance, but not stateful for the entire application's
existence.
We will create two separate classes within this AddressBookBiz namespace, one to represent an actual address book and another to
represent our contacts.
Let's drill down for each object and determine the methods and properties that we will expose.
AddressBook Object
Properties
Name Get Set Public
bookid yes no yes
name yes yes yes
description yes yes yes
relationshiplist* yes no yes
Methods
Name Description Parameters Public
Default Constructor Creates an empty instance of none yes
the object
Overloaded Constructor Loads up the appropriate bookid yes
address book
Overloaded Constructor Creates a new address book name, description yes
Contact
loadAddressBook Internal method to load up the bookid no
book. Called by other
methods
CanView Used to determine if the emailaddress yes
supplied user can view the
book
GetContactsDataSet Gets a DataSet of the none yes
contacts for the Address
object
AddNewContact Add a new contact to the Contact yes
current Address object
getLastError Simply returns the last error none yes
message
Validate Validates itself none yes
Save Persists the current Address none yes
book to the database
AddContact Adds a contact to the current Contact, Relationship yes
address book
Contact Object
Properties
Name Get Set Public
emailaddress yes no yes
password yes yes yes
first yes yes yes
last yes yes yes
areacode yes yes yes
phone yes yes yes
fax yes yes yes
address yes yes yes
address1 yes yes yes
city yes yes yes
zip yes yes yes
state yes yes yes
country yes yes yes
Methods
Name Description Parameters Public
Default Constructor Creates an empty instance of none yes
the object
Overloaded Constructor Loads up the appropriate emailaddress, yes
user (login) password
Overloaded Constructor Loads up the appropriate emailaddress yes
user
saveContact save the specific contact none no
isUserValid Determines if the supplied none yes
user is valid or not
getAddressBooks Returns all of the user's emailaddress yes
address books (not
preloaded)
getLastError Simply returns the last error none yes
message
Validate Validates itself none yes
Save Persists the current contact to none yes
the database (if we can)
* I’ve decided to place this method, which merely returns a list of the available relationships,
in the Address Book
object in order to keep this article short and simple. Normally you would probably make the
decision to create
a new, separate object that would perform the needed relationship actions.
** Since each Address Book object will contain a collection of Contact Objects, our Contact
Pointer is merely
a reference to the current contact in our list.
At this time, review my version of "AddressBookBiz.cs" (part of download). Pay attention to the Save method, and how it is first calling its
own Validate method in order to ensure that it conforms to the established rules. (This will validate the object at the object level). Calling
the Save method in the address book will validate the current address book, and then it will also call the Save method on each of its
contacts, which will also call its own Validate method. In the end, if everything validates fine, it will all be persisted to the database.
I want to quickly review what I did regarding security. In order to represent the current user (who is logged in), I needed a way to easily
represent that user based on their login information. I used a basic symmetrical encryption algorithm. This Namespace is based on the
Cryptography base classes within .NET. To integrate it within our "business" application, I only needed to add it to our application (see
previous examples on how to do this). Now, when the user is logged in, or authenticated against our database, I take their email address
and their password, and join them into a single string, and then apply the encryption algorithm. This generates a Universal Unique (user)
IDentifier (UUID). And when I need to determine who this UUID belongs to, I simply unencrypt the UUID, and then split the string back
into the email address and the password and re-authenticate against my database. I created this in this fashion in order to simplify the
security issues for this article.
I have completed most of the Business tier for you. I have left out some validation within each of the Property Get/Set methods. You
should conform each method to what you expect for this application. For example, the Area Code property could be limited to only valid
area codes (001 to 999), or the Country property could be limited to only a set of ISO-standard country codes. This has offered you a
place to put all of your server-sided data validation. Another thing you may consider is looking into the validation tools within the .NET
architecture. There are plenty of examples of this available on the Internet, just as long as you always keep in mind that validation MUST
be performed on the server, and in your middle tier. You can optionally add it on the client with JavaScript or whatever is specific to your
deployment platform. Adding validation to a single interface on the Presentation tier will only enable that specific interface to the system to
take advantage of that validation. Your other interfaces will not be able to take advantage of this.
Take time now to review each file and the code behind each. First look at the login.aspx page that allows the user to log in and get their
UUID, which gives them access to the rest of the application. Also, you may wish to start adding some functionality to the application. One
important issue would be a way to initially create a user when no address books are defined. Personally I would create an application
administrator (you), and create a global address book. And then each new user who is created (first time in) will be added to this global
address book. And then each of these users can create/edit their own address books, etc... Take the logic from the createuser.aspx page
and create your own, generic page to allow for this functionality. You could also consider adding in a feature to share contacts between
address books. All you would really need is another table to represent the intersection between the shared contacts and the appropriate
users.
By simply exposing the methods in our Business tier within the "AddressBookService" application, we can easily create a new interface
into our application based on XML Web Services (or any other way, like Windows Forms, etc...). I have started this interface for you, but
left it incomplete on purpose, to allow you to get your hands dirty by trying to port the rest of the Business tier to this new interface. All you
really need to know is that each user will start by logging into the application and getting their UUID. Then each subsequent request will
also pass this UUID in with the each method call. An alternative to this would be to use SOAP headers so that you do not need to expose
the methods with the additional UUID string on each method.