Git.Framework 框架隨手記--歷史原因

賀臣發表於2014-03-15

 

  Git.Framework 是近幾年工作的一些工作經驗總結,雖不能和某些知名的框架相提並論,但是還是比較實用的。此框架經過三年多的升級和維護,已經具有較強的實用性,在此記錄該框架的使用操作方式,貢獻給公司第一線開發的技術人員們,感謝你們所付出的努力。

 

  一. 框架由來

    前幾年我都是在網際網路公司工作,做什麼大型B2B,B2C網站。至於有多大就不提了,但是在其間做開發人員使用的技術的確比較辛苦,那個時候公司使用的技術還比較落後,最起碼我是這麼認為的,開發效率很低,一層不變的開發模式,Copy到想吐的程式碼。我是從事.NET技術開發的,當時公司還是使用的.NET2.0開發的,要知道2.0 和 3.0 差別還是挺大的,最起碼在語法上。

    據說公司的框架是參考某個大型B2C網站的,據說當時公司某技術君就是從那裡面出來的,使用微軟企業庫(Enterprise Library) , 於是一個B2C網站+ SNS社群 幾乎所有的資料訪問操作都是寫的SQL語句,而且都是配置在某個資料夾的配置檔案中。做過B2C或者SNS方面開發的可以想象得到有多少個SQL語句,當時我估計也有好幾千個吧。於是我就想在原有的技術框架上改進這個操作,經過世事變遷最終改的體無完膚也就是現在所謂的的框架(Git.Framework)了。

 

  二. 寫SQL語句複雜在哪裡

    先給大家看看當時我們的SQL語句的寫法,以及如何配置:

<dataCommand name="User.UpdateAllBase" database="Git" commandType="Text">
    <commandText>
      <![CDATA[
       UPDATE [Gas_BasicCenter].[dbo].[User_Base]
       SET [UserName] = @UserName
          ,[Email] = @Email
          ,[Password] = @Password
          ,[RegisterDate] = @RegisterDate
          ,[RegisterIp] = @RegisterIp
          ,[Status] = @Status
          ,[RegisterApplicationID] = @RegisterApplicationID
          ,[ActiveDate] = @ActiveDate
          ,[LastLoginDate] = @LastLoginDate
          ,[LastLoginApplicationID] = @LastLoginApplicationID
          ,[RegisterSource] = @RegisterSource
          ,[AuditStatus] = @AuditStatus
          ,[IsLogin] = @IsLogin
          ,[LoginCount] = @LoginCount
          ,[LastLoginIp] = @LastLoginIp
          ,[AuditUser] = @AuditUser
          ,[AuditDate] = @AuditDate
          ,[IsDeleted] = @IsDeleted
          ,[OLDApplicationID] = @OLDApplicationID
          ,[OLDID] = @OLDID
          ,[RowGuid] = @RowGuid
          ,[IMNum] = @IMNum
          ,[Phone] = @Phone
          ,[IsEmailValidate] = @IsEmailValidate
          ,[IsPhoneValidate] = @IsPhoneValidate
          ,[StepNum] = @StepNum
          ,[SaleCode]=@SaleCode
          ,[PasswordIM]=PasswordIM
          ,[ActiveIP]=@ActiveIP
          ,[CompanyType]=@CompanyType
            Where UserId=@UserId
        ]]>
    </commandText>
    <parameters>
      <param name="@UserId" dbType="Int32" direction="Input"/>
      <param name="@UserName" dbType="String" direction="Input"/>
      <param name="@Email" dbType="String" direction="Input"/>
      <param name="@Password" dbType="String" direction="Input"/>
      <param name="@RegisterDate" dbType="DateTime" direction="Input"/>
      <param name="@RegisterIp" dbType="String" direction="Input"/>
      <param name="@Status" dbType="Int32" direction="Input"/>
      <param name="@RegisterApplicationID" dbType="Int32" direction="Input"/>
      <param name="@ActiveDate" dbType="DateTime" direction="Input"/>
      <param name="@LastLoginDate" dbType="DateTime" direction="Input"/>
      <param name="@LastLoginApplicationID" dbType="Int32" direction="Input"/>
      <param name="@RegisterSource" dbType="Int32" direction="Input"/>
      <param name="@AuditStatus" dbType="Int32" direction="Input"/>
      <param name="@IsLogin" dbType="Int16" direction="Input"/>
      <param name="@LoginCount" dbType="Int32" direction="Input"/>
      <param name="@LastLoginIp" dbType="String" direction="Input"/>
      <param name="@AuditUser" dbType="String" direction="Input"/>
      <param name="@AuditDate" dbType="DateTime" direction="Input"/>
      <param name="@IsDeleted" dbType="Int16" direction="Input"/>
      <param name="@OLDApplicationID" dbType="Int32" direction="Input"/>
      <param name="@OLDID" dbType="Int32" direction="Input"/>
      <param name="@RowGuid" dbType="String" direction="Input"/>
      <param name="@IMNum" dbType="String" direction="Input"/>
      <param name="@Phone" dbType="String" direction="Input"/>
      <param name="@IsEmailValidate" dbType="Int32" direction="Input"/>
      <param name="@IsPhoneValidate" dbType="Int32" direction="Input"/>
      <param name="@StepNum" dbType="Int32" direction="Input"/>
      <param name="@SaleCode" dbType="String" direction="Input"/>
      <param name="@PasswordIM" dbType="String" direction="Input"/>
      <param name="@ActiveIP" dbType="String" direction="Input"/>
      <param name="@CompanyType" dbType="Int32" direction="Input"/>
    </parameters>
  </dataCommand>
SQL語句的配置

    情況1:看到上面的配置情況,很多人會聯絡到java中的某些框架,的確如此!個人對java涉略較少,後來在專門去查詢了相關的資料。先不討論SQL語句配置的格式,一個B2C網站+SNS社群需要多少這樣的配置節點,專案在不斷的增大,配置的難度到了讓人無法接受的程度,而且我們無法知道是否有人已經寫過一個同樣的SQL語句。有多少人的離開或多或少與這個有關係,那個時候系統出現bug我就是每天盯著螢幕ctrl+h 全域性查詢這樣的配置。

    情況2:SQL語句要每個自己去寫,轉化還必須要佔位符的方式,然後要填充引數(<param name="@UserId" dbType="Int32" direction="Input"/> 也就是這個配置)。當然這些都是手工體力勞動,不難當時的確會寫死人,比類檔案中直接寫SQL還要累。

    情況3: <dataCommand name="User.UpdateAllBase" database="Git" commandType="Text"> 配置項name在全域性是唯一的,而系統中有很多個這樣的配置檔案,在這個檔案中你查詢不到,並不代表其他的檔案沒有,要每一個檔案都去查詢一遍然後才能定義這個name值。 database 這個也存在問題,這個是關聯資料庫的,因為系統中存在多個資料庫,必須關聯這個sql在哪個庫裡面執行。 

    情況4:<parameters><param name="@UserId" dbType="Int32" direction="Input"/></parameters> 引數項的配置太多,特別是表的欄位多的時候,修改好煩躁。有時候修改一個欄位,有時候修改其中幾個欄位,要分別寫不同的SQL來執行。

    情況N .... 就不多寫了,各種問題足以讓人抓狂,拿人錢財替人寫程式碼天經地義的事情所以沒辦法,硬著頭皮寫。

 

  三. 以上問題的改進

    那個時候自己還是小兵啊,人微言輕的,也不敢提框架不好使,不然大牛會生氣,後果很嚴重。想想也是完成好自己的工作就好了。

    配置問題的解決:

      上面貼出來的這個配置檔案,其實一個定義好的標準格式,包括裡面的參考型別以及名稱都是定義好的,後來我使用CodeSmith自定義模板生成,很快速的結果手工寫模板的問題,但是有一個問題就是模板沒有那麼靈活,我需要指定哪些欄位要使用哪些不能使用,不是那麼自能,只能全部給我生成出來,不過也算是方便了不少。

    實際問題考慮:

    (1) 登入系統,兩個步驟: 1. 查詢使用者名稱和密碼 2. 查詢使用者詳細資訊

    都是使用User表,我們可以查詢User表的所有資料,但是往往登入的時候我們只會查詢使用者名稱和密碼,查詢使用者詳細的時候又不查詢密碼,甚至還有許可權的問題,網路傳輸的問題。只能查詢當前業務操作所需的欄位。

SELECT [UserNo],[UserPwd] FROM [dbo].[SysUser]

SELECT [UserNo],[UserCode],[UserName],[UserPwd],[IsDelete] FROM [dbo].[SysUser]

    以上兩個SQL語句有區別麼,肯定有區別,查詢的雖然是同一張表,但是返回的資料量大小不一樣,為什麼要這麼做? 後來做資訊化管理系統之後,也就不管這麼多了都直接使用 Select * From Table 直接代替了。

    (2) 修改個人資料資訊

    

    以前工作的專案圖片不好找了,直接以部落格園的為例。 假設使用者頭像,暱稱,姓名,聯絡方式,畢業院校等資訊是保持在User表中(當然現在很多資訊也有是分表處理的,暫且不談,這是資料結構設計問題)的。看上面的截圖,使用者資訊的維護每次使用者資料的提交併不是提交了所有的個人資訊資料,而只是部分提交的。

Update [dbo].[SysUser] Set Picture=@Picture where UserNo=@UserNo

Update [dbo].[SysUser] Set LoginCount=@LoginCount,UserName=@UserName where UserNo=@UserNo

    諸如以上的這種太多的有共性但是又有區別的SQL語句太多了。

    後來使用的方式就是,先將某條資料全部查詢出來,封裝為一個Entity,然後修改Entity中的某個值,然後提交所有的Entity值,也就是修改表中的每一個欄位,雖然處理的方式不好,但是比寫N個這樣的Sql語句配置省事多了。

 

   四. 如何檢查配置項的重複問題

    頗為頭疼的就是配置項name值的重複問題,N多個配置檔案在某一個資料夾下,而資料夾中又有資料夾,查詢不方便。

    軟體的作用是什麼,這個問題太過於廣泛,但是有一點軟體能夠省人工,這個我是贊同。如何去查詢重複項,那麼就寫一個小程式去區分好了。程式非常簡單就是讀取所有的配置檔案,然後盤點是否有相同的項,這裡不必多說,但是這個時候讓我學到一個技能,就是如何寫VS外掛。

    之前寫的工具已經不復存在,剛才在網上隨便找了一篇文章關於這個的介紹,有興趣可以參考一下:

    http://blog.csdn.net/clever101/article/details/8733799

 

  五. 實質性的問題如何解決

    以上種種手段都是暫時的解決問題,並沒有達到治本的目的。於是自己開始籌劃改進公司的技術框架。

    當時公司的技術體系就是這樣,基於以前的工作經驗,你想要去推翻公司的技術體系然後再重新來過是不太可能的,而且這個想法也是過於天真的,先不說你是否有這個能力,就算你有公司也不會冒這樣的風險。

    當然當時我也提過技術框架的改進,當時領導評估風險太大,只能慢慢的去維護,也就是公司不可能專門設計組織或專案來搞這個東西,所以我只能自己業餘時間來了。

    1. 資料操作使用ORM是不可避免的方向

      當時推崇過一段時間的Linq to SQL,包括Entity Framework ,也給公司提過,公司堅決的否定了,而後來我也對此嗤之以鼻因為其臃腫度,也不知道我是否理解錯了。研究了好長一段時間的Linq to SQL ,Entity Framework 後來也不不再理這個。

      對比了一些較為常見的ORM,無論怎麼吹噓怎麼怎麼強大但都有其天生的缺陷性,沒有一個是萬能的。後來我也承認這些東西不可能是萬能的,所謂真正好的框架是在實際的業務中提煉出來的,而不是你想怎樣就能幫你解決問題的。

      加上要相容公司原有的技術體系,所以這個只能改進不能重新來過。

    2. 去配置化

      那個時候還年輕啊,聽老前輩說配置化如何的牛逼,特別是java程式設計師,java中的配置化多麼牛逼,系統如何的靈活! 現在我始終認為那是一個騙局,要麼是我沒有理解其精髓,要麼他們也就是照書上照本宣科的胡亂說一通。這不是重點,重點是如果我改進一定要去配置化,因為配置已經讓我們身處火熱之中。

    3. 蛋疼的跨資料庫查詢

      如果在同一個資料庫例項中,兩個或者多個資料庫跨庫查詢貌似還比較好解決,如果涉及到跨網路腫麼辦,貌似沒有ORM專門去解決這個問題,或許是我孤陋寡聞了。在公司的改進中我提議是跨庫查詢還是在記憶體中去處理,就是多次去查詢資料庫然後記憶體整合資料,後來也證明了我還是有點明智的,多資料庫分離到不同的伺服器上了。

    

  六. 本文小結

    在平時的工作中還有很多類似的問題,不知道是因為自己遇到的東西少了還是其他的原因,都是一些非常低階而又苦惱的問題,真正有什麼科研技術的問題我真沒有遇到。決定寫這個文章只是為了總結一下自己這些年的工作,覺得有很多地方是值得借鑑的。

    1. 在一個技術體系已經成型的團隊裡面,不要妄想去推翻現有的技術體系,雖然這樣的體系問題非常多,多到讓你無法接受,你要做的是改進,去提升,而不是抱怨問題太多。"要麼忍要麼滾" 這是我的格言。

    2. 問題多是一個團隊的正常表現,如果沒有問題團隊也就不需要你了。

    3. 只要是問題肯定就會有解決的辦法,如果你自己沒有去嘗試過,不要輕易去評判別人的對與錯,否則就閉嘴。

    4. 剛出道或者剛進公司不要認為領導都是傻逼,如果你有這種想法只能說明你比他們更傻逼,幾年前我也站在罵領導是傻逼的行列。

    5. 任何一個公司的技術體系都有值得你去借鑑學習的,只要它擺在那裡就不會是垃圾。

 

    如今自己做實施了,雖然程式還寫,相比之前心情放鬆了很多,不是為了丟棄程式碼,只是有時候該換一種方式去思考或許程式會寫的更好一點。

 

    成功的定義永遠只有一個,那就是對結果負責!

 

    

相關文章