几个重要的句子:
1,不要在考虑系统中究竟有多少个层次上花时间甚至挂死,这根本就是浪费原本可以做实际工作的时间。Don't get hung up on academic concerns such as how many tiers you have in your system. You'll just waste the time you could spend doing things you actually need to do.
2,两个层次划分的纯度:
- 契约的纯度(在各层次之间有一组众所周知的接口)
- 二元纯度(各个层次之间的类型是完全分开的,并且各自之间有一个契约)
作者认为大多数人都过多的在二元纯度上进行考虑,而他认为这根本没有作用。在一个简单的项目中(他将WebService和Web client认为是两个项目)建立并行的对象集不会有任何收获。
作者表示很喜欢契约的纯度(这才是N-Tier模型的真谛),问题在于如何定义契约,并且这就要看你自己了。LINQ to SQL是将逻辑和数据紧密结合在一起了(Logic == Data)。不过在大多数情况下,大部分人都对DataContext进行封装,“封装Data Context就是建立逻辑层”。
LINQ and 3-Tier Dogma
One of the most frequent questions we've received about LINQ to SQL deals with fitting it into the classic three-tiered scenario. That is:
Presentation --- Logic --- Data
I know someone somewhere is going to accuse me of heresy for what I'm about to state, but it's something I've wanted to get off my chest for a couple years, since I first encountered LINQ to SQL, and started to realize what it meant...
The 3-Tier Model is just a pattern.
And, like all patterns,
It is meant to serve our needs, not the other way around.
Don't get hung up on academic concerns such as how many tiers you have in your system. You'll just waste the time you could spend doing things you actually need to do. Such as watching Avatar or playing Warcraft.
There, I said it.
And saying that, I think LINQ to SQL fits perfectly well into the 3-Tier Model.
The spirit of the 3TM is that, in keeping your layers seperate, you achieve the ability to swap out data access, business logic, or presentation interfaces without disturbing the other two (or the functionality that depends on them). This, without dispute, is a useful thing. But there are various levels of purity that people seem to think apply:
- contractual purity (there is a set of well-known interfaces between the tiers)
- binary purity (the types are completely seperate in each tier, *and* there is a contract between each)
Frequently, I've seen people get hung up on the idea of binary purity. I'll say it: I don't think it honestly matters. Within the scope of a single project (and I'll submit that a web service and a web client are in fact two different projects), there is no gain in creating parallel sets of objects just for the sake of doing so. That is, there is no longer a compellingly good reason, and in fact I now consider it questionable design, in having "ProductDataObject" and "ProductLogicalObject" in the same project, when the shapes of the two are the same. You'll spend all your time copying property values back and forth, your references won't track well, and you'll forgo any change- or identity- tracking that the data layer happens to give you.
Don't underestimate the value of tracking features in a data layer.
So, let's just ditch the idea of binary purity for purity's sake; it's only real use is when transmitting data packets to foreign systems. Instead, let's talk about contractual purity.
Contractual purity is great. I like it a lot, and I think this is where the true heart of n-tier modelling is to be found. The only question is how you define your contracts.
Really, that's up to you.
Consider LINQ to SQL for a moment. LINQ to SQL was designed for those situations where the schema of your database matches the shape of data in what we'll loosely call your "logic" or "business" layer. (Loosely, because there can be many layers to this, or none at all for a dumb reporting app.) That is:
Presentation --- Logic --- Data
A --- B --- BLINQ to SQL presents us with this:
A --- B === B
LINQ to SQL's already encapsulated your database access code (that is why it was written); it's all contained in System.Data.Linq.dll and your mapping metadata. It also already talks in terms of your logical model, and that's fine because we already said that was what LINQ to SQL was designed to do.
That leaves us with the question of what we use for presentation. Again, that's entirely up to you. If your project is such that is doesn't require formal contracts, and if the data being displayed has the same shape as what's available, why *not* use your logical model directly?
ui.DataSource = db.Customers;
B === B === B
More often, of course, you'll be displaying composite data, and the shape of that will be somewhat different than your logical data. Fine. We can do that: project into anonymous types (eg, if you don't need to access the properties directly -- if your UI does reflection), or project into a contractually-defined type, and return that:
ui.DataSource = db.Customers.Select(c => new { c.LastName, c.FirstName });
ui.DataSource = db.Customers.Select(c => new CustomerName { Last = c.LastName, First = c.First });A --- B === B
Ah, you say. You feel its sinful to even expose the tables on your data context. Okay, fine. Remove them. In fact,
Encapsulate your data context.
That is,
public class MyLogicLayer: IDisposable // MyLogicLayer is IDisposable because MyDataContext is IDisposable
{// We retain an instance of the context. It's not static, because contexts are designed to be short-lived.
MyDataContext db;// We want to update data, right?
public void SubmitChanges()
{db.SubmitChanges();
}
// I prefer IQueryable, because I want my filtering and sorting to happen at the server, not the client.
// That is, I want to write: logicLayer.GetCustomersInWa().OrderBy(c => c.Age)
public IQueryable<Customer> GetCustomersInWA()
{return db.Customers.Where(c => c.State == "WA").OrderBy(c => c.LastName).ThenBy(c => c.FirstName);
}
// TVFs are a much more useful alternative to sprocs. In particular, they're composable.
public IQueryable<Customer> TVF_GetCustomersInWA()
{return db.GetCustomersInStateTVF("WA").OrderBy(c => c.LastName).ThenBy(c => c.FirstName);
}
// Of course, getting a single customer is useful
public Customer GetCustomer(int id)
{return db.Customers.Where(c => c.CustomerId == id);
}
// Maybe some admin doesn't want you to know about the customer's birthdate. So make a trimmed-down ICustomer,
// and return based on that interface.
public ICustomer GetCustomer(int id)
{// where the interface is sufficient protection
return db.Customers.Where(c => c.CustomerId == id);or
// where CustomerWithoutBirthdate implements ICustomer, and whose properties defer to the contained Customer
return db.Customer.Where(c => c.CustomerID == id).Select(c => new CustomerWithoutBirthdate(c));}
}
Which, interestingly, looks like:
A === [ A --- B ] === B
B === [ B === B ] === B
B' === [ B' === B ] === B.. depending on which method you call. In other words,
The encapsulation of the data context is the creation of the logical layer.
I'm sure that in the upcoming years, there will be plenty of other ways people will find to adapt the traditional architectures to new innovations, and I'm certain there will be further innovations that make what I just proprosed easier to swallow. And that's what makes programming interesting. If we have indeed solved all the problems and programming is perfect forever, where's the fun in that?
Published Tuesday, November 06, 2007 4:15 PM by kfarmer@microsoft.com
Comments
# re: LINQ and 3-Tier Dogma
It's all good, until you need stored procs or UDF.
I didn't find a way to map a udf without using DataContext derived class. Keeps throwing exceptions if the method is declared not in the DataContext.
Am I missing something?
Tuesday, March 11, 2008 10:43 AM by agornik