十年磨一劍--從程序員到架構師

一个.net程序员,一个企业应用的开发者,喜欢系统架构,数据库,领域驱动,面向对象,表现层技术。关注重用的理论和实践。设计原则:简单,快速,适应变化能力强,表现层灵活多变...

博客园 首页 新随笔 联系 订阅 管理
  49 Posts :: 0 Stories :: 721 Comments :: 26 Trackbacks

公告

三层架构已经被说了很多次,今天来谈谈我的看法。

 .net下典型的三层架构就是UI,业务逻辑层和数据访问层。

 UI层暂且不表,就谈谈争论最大的业务逻辑层和数据访问层吧,这是争论最多,也是被误解的最多的一点。

 petshop4为例,什么都先别说,一看代码就闻到了bad smell,为什么,看看那个IDal,SqlServerDAL,还有OracleDA),到处都是重复的代码,下面几个随便找的方法:

 

        public IList<ProductInfo> GetProductsBySearch(string[] keywords) {

            IList<ProductInfo> productsBySearch = new List<ProductInfo>();

            //Create a new query string

            int numKeywords = keywords.Length;

            StringBuilder sql = new StringBuilder(SQL_SELECT_PRODUCTS_BY_SEARCH1);

            //Add each keyword to the query

            for (int i = 0; i < numKeywords; i++) {

                sql.Append(string.Format(SQL_SELECT_PRODUCTS_BY_SEARCH2, PARM_KEYWORD + i));

                sql.Append(i + 1 < numKeywords ? SQL_SELECT_PRODUCTS_BY_SEARCH3 : SQL_SELECT_PRODUCTS_BY_SEARCH4);

            }

 

            //See if we have a set of cached parameters based on a similar qquery

            string sqlProductsBySearch = sql.ToString();

            OracleParameter[] parms = OracleHelper.GetCachedParameters(sqlProductsBySearch);

            // If the parameters are null build a new set

            if (parms == null) {

                parms = new OracleParameter[numKeywords];

                for (int i = 0; i < numKeywords; i++)

                    parms[i] = new OracleParameter(PARM_KEYWORD + i, OracleType.VarChar, 80);

                // Cache the new parameters

                OracleHelper.CacheParameters(sqlProductsBySearch, parms);

            }

            // Bind the new parameters

            for (int i = 0; i < numKeywords; i++)

                parms[i].Value = keywords[i];

            //Finally execute the query

            using (OracleDataReader rdr = OracleHelper.ExecuteReader(OracleHelper.ConnectionStringLocalTransaction, CommandType.Text, sqlProductsBySearch, parms)) {

                while (rdr.Read()) {

                    ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), rdr.GetString(2), rdr.GetString(3), rdr.GetString(4));

                    productsBySearch.Add(product);

                }

            }

            return productsBySearch;

        }

 

 

        public void Insert(OrderInfo order) {

            int orderId = 0;

            // Get the parameters

            OracleParameter[] completeOrderParms = null;

            OracleParameter[] orderParms = GetOrderParameters();

            OracleParameter statusParm = new OracleParameter(PARM_ORDER_ID, OracleType.Number);

            // Bind the parameters

            orderParms[1].Value = order.UserId;

            orderParms[2].Value = order.Date;

            orderParms[3].Value = order.ShippingAddress.Address1;

            orderParms[4].Value = order.ShippingAddress.Address2;

            orderParms[5].Value = order.ShippingAddress.City;

            orderParms[6].Value = order.ShippingAddress.State;

            orderParms[7].Value = order.ShippingAddress.Zip;

            orderParms[8].Value = order.ShippingAddress.Country;

            orderParms[9].Value = order.BillingAddress.Address1;

            orderParms[10].Value = order.BillingAddress.Address2;

            orderParms[11].Value = order.BillingAddress.City;

            orderParms[12].Value = order.BillingAddress.State;

            orderParms[13].Value = order.BillingAddress.Zip;

            orderParms[14].Value = order.BillingAddress.Country;

            orderParms[15].Value = order.OrderTotal;

            orderParms[16].Value = order.BillingAddress.FirstName;

            orderParms[17].Value = order.BillingAddress.LastName;

            orderParms[18].Value = order.ShippingAddress.FirstName;

            orderParms[19].Value = order.ShippingAddress.LastName;

                        orderParms[20].Value = order.AuthorizationNumber.Value;

            // Create the connection to the database

            using (OracleConnection conn = new OracleConnection(OracleHelper.ConnectionStringOrderDistributedTransaction)) {

                // Open the database connection

                conn.Open();

                // Get the order id for the order sequence

                orderId = Convert.ToInt32(OracleHelper.ExecuteScalar(conn, CommandType.Text, SQL_GET_ORDERNUM));

                orderParms[0].Value = orderId;

                statusParm.Value = orderId;

                // Total number of parameters = order parameters count + 1 + (5 * number of lines)

                int numberOfParameters = orderParms.Length + 1 + (5 * order.LineItems.Length);

                //Create a set of parameters

                completeOrderParms = new OracleParameter[numberOfParameters];

                //Copy the parameters to the execution parameters

                orderParms.CopyTo(completeOrderParms, 0);

                                completeOrderParms[orderParms.Length] = statusParm;

                //Create a batch statement

                StringBuilder finalSQLQuery = new StringBuilder("BEGIN ");

                // Append the order header statements

                finalSQLQuery.Append(SQL_INSERT_ORDER);

                finalSQLQuery.Append("; ");

                finalSQLQuery.Append(SQL_INSERT_STATUS);

                finalSQLQuery.Append("; ");

                                int index = orderParms.Length + 1;

                int i = 1;

                // Append each line item to the batch statement

                foreach (LineItemInfo item in order.LineItems) {

                    //Add the appropriate parameters

                    completeOrderParms[index] = new OracleParameter(PARM_ORDER_ID + i, OracleType.Number);

                    completeOrderParms[index++].Value = orderId;

                    completeOrderParms[index] = new OracleParameter(PARM_LINE_NUMBER + i, OracleType.Number);

                    completeOrderParms[index++].Value = item.Line;

                    completeOrderParms[index] = new OracleParameter(PARM_ITEM_ID + i, OracleType.Char, 10);

                    completeOrderParms[index++].Value = item.ItemId;

                    completeOrderParms[index] = new OracleParameter(PARM_QUANTITY + i, OracleType.Number);

                    completeOrderParms[index++].Value = item.Quantity;

                    completeOrderParms[index] = new OracleParameter(PARM_PRICE + i, OracleType.Number);

                    completeOrderParms[index++].Value = item.Price;

                    // Append the statement to the batch

                    finalSQLQuery.Append(string.Format(SQL_INSERT_ITEM, i));

                    finalSQLQuery.Append("; ");

                    i++;

                }

                //Close the PL/SQL block

                finalSQLQuery.Append("END;");

                // Finally execute the query

                OracleHelper.ExecuteNonQuery(conn, CommandType.Text, finalSQLQuery.ToString(), completeOrderParms);

            }

        }

 

不知道你感觉如何,反正我受不了。

 

这些代码如果叫我去写,我不愿意,有人说代码生成器生成(一个优秀的系统架构是很少需要什么代码生成器的,因为重复的地方已经作了抽象,剩下需要写的确实是必须for具体的情况来开发的,如果你的代码需要代码生成器,那你就应该考虑有没有东西需要抽象出来重用了)

 

透过现象看本质,这些代码无非就是在作几件事情,获取参数,组织sql使用ado.net访问数据库,如果有结果则作一个mapping到对象。

 

代替这几个项目的功能其实非常简单。

 

首先只要有一个稍微封装了ado.net的类(类似于SqlHelper的东东),然后一个产生command对象的模块(当然其实就是产生sqlprocedure,然后设置参数,其参数可以非常简单地进行替换,也可以使用SqlParameter传入,如何更灵活,可以试试自己的抽象能力和设计功底),sql可以配置,不过也可以傻得像petshop那样类里面变量hardcode起来。

Like this:

private const string SQL_SELECT_CATEGORIES = "SELECT CategoryId, Name, Descn FROM Category";

private const string SQL_SELECT_CATEGORY = "SELECT CategoryId, Name, Descn FROM Category WHERE CategoryId = @CategoryId";

private const string PARM_CATEGORY_ID = "@CategoryId";

 

最后你可能说怎么切换数据库。在我开发了这么多系统,还没有一个系统有这样的要求,当然可能我从事的是for单个企业的软件开发(不过我们公司sql serveroracle都有,而且都常用)。某些做产品的TX可能确实有这个需求,因为要迁就客户。这个问题也是非常容易解决的。

 

只是需要知道,不是什么重用,变化都需要用到接口+override才能实现的。

 

 

IOC相信很多人都听过,可是真正理解的又有几人呢~~

IOC干的就是这事,替换对象是小菜一碟。

在这里要切换数据库,只需要用IOC方式SqlHelper换成OracleHelper就行了(当然如果架构师又没有洞察力,没有将对象创建和使用分开,那就另当别论了。简单说一个OO中的开发原则,创建对象的不要使用对象,使用对象的不要创建对象,分开来,且最好将创建对象部分封装成功能赶强的基础模块,springcastle的核心干的就是这事,不过我还是喜欢自己来,因为有的东东它们没有提供,而提供的又太多了,我用不着,裁剪一下,最简单,最适合的就是最好的,现在太多框架都是这样的,动不动就搞个赶级复杂的东东出来,有必要吗?像NbearLite的架构是我喜欢的类型,那个mapping层,动态代码产生层,数据访问层是明显分开的,合我胃口,不过有个小意见,能不能提下,mapping层能不用dataaccess层吗,虽然我知道你封装一些方法是为了让我更好地调用,谢谢~~ 但是可以使用一个继承的项目来添加这些功能,因为我有的系统只想用dataaccess功能,有的系统只想用你超级爽的mapping功能,sorry,跑题了)

 

好了,点一下题,数据访问层其实只有一个SqlHelper类或OracleHelper类,非常简单。其它都属于真正的业务逻辑部分(关于业务逻辑,以后有空再谈,这些都是一些被用滥的词汇)

 

记得在园子里某位TX的博上看到一句话:MS的数据访问层最有意义的就是那个SqlHelper类,对此,我深表赞同。

 

其实划分为业务逻辑层和数据访问层99%是没有半点意义的。

比起这个分层模式更有意义的是缓存设计,验证框架,异常处理,对象构造UI接口,事务处理,日志记录等等的设计

 

因为相同的事情只作一次永远是一个合格的系统架构师首先要遵循的原则,而透过现象看到事物的本质则是对他架构系统最基本的要求。

 

反正我是不愿意在PetShop架构师下写代码,除非MS白养我,刚好我头痛,不能也不想思考的时候,就会机械地敲敲这些无聊的代码(当然这么多代码要开发,总要给我时间吧,心情好的时候,翠花,上代码生成器,好省下时间上cnblogs

 

什么?系统有变化?不好意思,我只是一个小小程序员,只负责完成我的任务,其它的找架构师吧~~

 

(原谅我用这么强烈的带有主观色彩的词句,因为我觉得在阐述自己的观点时,最起码要旗帜鲜明,观点明确,否则写和看那些可说可不说的废话,真的会浪费你我宝贵的时间)

 

posted on 2009-06-01 12:23 Kevin Zou 阅读(...) 评论(...) 编辑 收藏