在進行架構設計時,善用一些 Third-Party 的工具或是元件,也可以達到筆者講的:找出最省時、省力、又保有較佳的設計、維護姓、時效性,又兼顧團隊的 Skill、以後維護也方便的一種方式。因為架構設計有時候也是一種全盤的考慮,不光是技術,有時,天、時、地、利、人和、環境,也會是考慮因素之一。
前言
有讀者告訴我,這個架構其實 Controller 還是得 New Services 的相關實體,只是概念上的分層,看起來程式碼分開了,可是 Web 端產生實體的方式還是限制了這個架構的延展性,而火紅的 IoC 、DI 為什麼沒有用進來呢?而且軟體架構的分層也也不該是直接將 Interface 整理成一個 Assembly,是的,沒有錯,其實這個架構只是單存應付之前公司內部直接應用的架構,因為考慮許多專案成員並不懂 IoC 與 DI 的概念,但又必須在短時間內導入、讓大家快速的上手、提升專案的可維護性,而暫實作的架構。
使用 Autofac
如果要將這個架構改變為支援 Autofac 會很難修改嗎?答案是:並不會,如果前面的基礎您已經有了,那麼許多新的東西其實都只是個概念,這也是筆者一直在說的,『基礎只要打穩,那麼學新的東西都只是學概念而已。』
如何開始?
一、修改 Cus.DataAccess
將原本在 Cus.Models 下面的 Entities 資料夾搬至 Cus.DataAccess,因為原先的 Cus.DataAccess 中的 DalCusOrders.cs 的 AddOrder() 方法中使用了一個 Orders 的 Entity,為了不修改原有的DalCusOrders.cs ,也避免循環參考的問題,所以將 Entities 搬至 Cus.DataAccess 中。
二、改造 Cus.Models
這個步驟會較為繁瑣一些。
A. 建立 IDbContextFactory 介面
1: public interface IDbContextFactory
2: {
3: DalCusOrders GetDbContext();
4: }
並建立 DbContextFactory 類別來實作資料來源,使用 Cus.DataAccess 的 DalCusOrders.cs 作為主要的實際來源來源。因為我們必須在不修改原 Cus.DataAccess 的情況下完成 Autofac 模式的修改。
接著撰寫如下程式碼:
1: using Cus.DataAccess.Order;
2: using System;
3: using System.Collections.Generic;
4: using System.Linq;
5: using System.Text;
6: using System.Threading.Tasks;
7:
8: namespace Cus.Models.DbContextFactory
9: {
10: /// <summary>
11: ///
12: /// </summary>
13: public class DbContextFactory : IDbContextFactory
14: {
15: private string _ConnectionString = string.Empty;
16: public DbContextFactory(string connectionString)
17: {
18: this._ConnectionString = connectionString;
19: }
20: private DalCusOrders _dbContext;
21: private DalCusOrders dbContext
22: {
23: get
24: {
25: if (this._dbContext == null)
26: {
27: Type t = typeof(DalCusOrders);
28: this._dbContext = (DalCusOrders)Activator.CreateInstance(t);
29: }
30: return _dbContext;
31: }
32: }
33: public DalCusOrders GetDbContext()
34: {
35: return this.dbContext;
36: }
37: }
38: }
如上程式碼,回傳的 DbContext 其實就是 Cus.DataAccess 的 DalCusOrders.cs 類別。
B. 加入 GenericRepository 與 UnitOfWork 模式
這裡會使用既有的 IRepository 介面,與 Repository Pattern 的 GenericRepository 來實作實際的資料存取,實際的資料來源就由外部注入 IDbContextFactory 介面。
由於之後對於 DB 實際存取的實作會由 GenericRepository.cs 來提供,所以,將原先需實作的相關介面都加入到 IRepository<T> 中。GenericRepository 設計類別圖如下圖:
其中,其實有許多沒有使用到的,如:原本定義在 IRepository<T> 中的 Add、Del、Edit 等方法。由於這些方法沒有使用到,所以筆者就不加以實作其內容。
IRepository<T> 的程式碼如下:
1: public interface IRepository<T> where T : class
2: {
3: /// <summary>
4: /// 新增一筆資料
5: /// </summary>
6: /// <param name="entity"></param>
7: int Add(T entity);
8: /// <summary>
9: /// 刪除一筆資料
10: /// </summary>
11: /// <param name="id"></param>
12: /// <returns></returns>
13: int Del<K>(K id);
14: /// <summary>
15: /// 編輯一筆資料
16: /// </summary>
17: /// <param name="entity"></param>
18: /// <returns></returns>
19: int Edit(T entity);
20: /// <summary>
21: /// 取得所有資料
22: /// </summary>
23: /// <returns></returns>
24: IQueryable<T> GetAll();
25:
26: #region CustomerService
27: /// <summary>
28: /// 取得特定客戶的所有訂單.
29: /// </summary>
30: /// <param name="CusID"></param>
31: /// <returns></returns>
32: IEnumerable<CusOrders> GetByCusID(string CusID);
33: /// <summary>
34: /// 取得所有的 Customer, ContactName 的連絡資訊.
35: /// </summary>
36: /// <returns></returns>
37: IEnumerable<Customers> GetCustomerList();
38: /// <summary>
39: /// 使用客戶代碼取得客戶的聯絡人名稱
40: /// </summary>
41: /// <param name="CustomerId"></param>
42: /// <returns></returns>
43: object GetCustomerByCustomerID(string CustomerId);
44: #endregion
45:
46: #region EmployeeService
47: /// <summary>
48: /// 取得產品負責人員工清單
49: /// </summary>
50: /// <returns></returns>
51: IEnumerable<Employees> GetEmployees();
52: #endregion
53:
54: #region OrderService
55: /// <summary>
56: /// 新增一筆訂單資料.
57: /// </summary>
58: /// <param name="orders"></param>
59: /// <returns></returns>
60: int AddOrder(Orders orders);
61: #endregion
62:
63: #region ProductService
64: /// <summary>
65: /// 使用 ProductID 取得 ProductPrice.
66: /// </summary>
67: /// <param name="ProductID"></param>
68: /// <returns></returns>
69: object GetProductPriceByProductID(int ProductID);
70: /// <summary>
71: /// 取得產品清單
72: /// </summary>
73: /// <returns></returns>
74: IEnumerable<Products> GetProducts();
75: #endregion
76:
77: #region ShipperService
78: /// <summary>
79: /// 取得貨運清單
80: /// </summary>
81: /// <returns></returns>
82: IEnumerable<Shippers> GetShippers();
83: #endregion
84: }
在 中,在 Cnstructor 中注入 IDbContextFactory 介面,所以在 GenericRepository 並不需要實際的 DB 實作從哪裡過來。而注入的部分,待會會透過 IoC 的 Autofac 來實作這個部分。
GenericRepository 的程式碼如下:
1: public class GenericRepository<TEntity> : IRepository<TEntity>
2: where TEntity : class
3: {
4: private DalCusOrders _context { get; set; }
5: private IDbContextFactory _factory;
6:
7: public GenericRepository(IDbContextFactory factory)
8: {
9: if (factory == null)
10: {
11: throw new ArgumentException("IDbContextFactory is null!.");
12: }
13: this._factory = factory;
14: this._context = factory.GetDbContext();
15: }
16:
17: public IQueryable<TEntity> GetAll()
18: {
19: //return _context.Set<TEntity>().ToList().AsQueryable();
20: return null;
21: }
22:
23: public int Del<K>(K id)
24: {
25: throw new NotImplementedException();
26: }
27:
28:
29: public int Add(Customers entity)
30: {
31: throw new NotImplementedException();
32: }
33:
34: public int Edit(Customers entity)
35: {
36: throw new NotImplementedException();
37: }
38:
39: public IEnumerable<CusOrders> GetByCusID(string CusID)
40: {
41: return DbUtility.DataUtil.GetEnumerableByDataTable<CusOrders>(_context.GetByCusID(CusID));
42: }
43: /// <summary>
44: ///
45: /// </summary>
46: /// <returns></returns>
47: public IEnumerable<Customers> GetCustomerList()
48: {
49: return DbUtility.DataUtil.GetEnumerableByDataTable<Customers>(_context.GetCustomerList());
50: }
51: /// <summary>
52: ///
53: /// </summary>
54: /// <param name="CustomerId"></param>
55: /// <returns></returns>
56: public object GetCustomerByCustomerID(string CustomerId)
57: {
58: return _context.GetCustomerByCustomerID(CustomerId);
59: }
60: /// <summary>
61: ///
62: /// </summary>
63: /// <returns></returns>
64: public IEnumerable<Employees> GetEmployees()
65: {
66: return DbUtility.DataUtil.GetEnumerableByDataTable<Employees>(_context.GetEmployees());
67: }
68:
69: public int AddOrder(Orders orders)
70: {
71: return _context.AddOrder(orders);
72: }
73:
74: public object GetProductPriceByProductID(int ProductID)
75: {
76: return _context.GetProductPriceByProductID(ProductID);
77: }
78:
79: public IEnumerable<Products> GetProducts()
80: {
81: return DbUtility.DataUtil.GetEnumerableByDataTable<Products>(_context.GetProducts());
82: }
83:
84: public IEnumerable<Shippers> GetShippers()
85: {
86: return DbUtility.DataUtil.GetEnumerableByDataTable<Shippers>(_context.GetShippers());
87: }
88:
89: public int Add(Employees entity)
90: {
91: throw new NotImplementedException();
92: }
93:
94: public int Edit(Employees entity)
95: {
96: throw new NotImplementedException();
97: }
98:
99: public int Add(Orders entity)
100: {
101: throw new NotImplementedException();
102: }
103:
104: public int Edit(Orders entity)
105: {
106: throw new NotImplementedException();
107: }
108:
109: public int Add(Products entity)
110: {
111: throw new NotImplementedException();
112: }
113:
114: public int Edit(Products entity)
115: {
116: throw new NotImplementedException();
117: }
118:
119:
120: public int Add(Shippers entity)
121: {
122: throw new NotImplementedException();
123: }
124:
125: public int Edit(Shippers entity)
126: {
127: throw new NotImplementedException();
128: }
129:
130: public int Add(TEntity entity)
131: {
132: throw new NotImplementedException();
133: }
134:
135: public int Edit(TEntity entity)
136: {
137: throw new NotImplementedException();
138: }
139: }
如上的程式碼中,有許多是沒有實作其內容的,由於只是 DEMO,筆者先演示結果給各位看,各位也可在 IRepository<T> 將其移除,這裡就不需要實作這些方法。
程式碼中使用到的 GetEnumerableByDataTable<T> 方法是我的 Gelis DAL Framework 中所提供的一個 DataTable 轉 IEnumerable<T> 的方法。這個 DbUtility 引用的方式如下:
1: using GelisFrameworks.Data.DAL;
2: using System;
3: using System.Collections.Generic;
4: using System.Linq;
5: using System.Text;
6: using System.Threading.Tasks;
7:
8: namespace Cus.Models
9: {
10: /// <summary>
11: /// 提供與資料相關的附加功能. Use Gelis DAL Framework.
12: /// </summary>
13: public class DbUtility
14: {
15: #region Gelis DAL Framework
16: private static MSSQLObject _MSSql = null;
17: public static MSSQLObject DataUtil
18: {
19: get
20: {
21: if (_MSSql == null)
22: _MSSql = new MSSQLObject(new GelisFrameworks.Data.DAL.DataAccess());
23: return _MSSql;
24: }
25: }
26: #endregion
27: }
28: }
C. 修改原本的 ICustomer、IEmployee、IOrder、IProduct、IShipper 介面
還有一件事沒有做,也就是修改原本的介面名稱:
ICustomer => ICustomerRepository
IEmployee => IEmployeeRepository
IOrder => IOrderRepository
IProduct => IProductRepository
IShipper => IShipperRepository
D. 建立 IUnitOfWork 模式
接下來,GenericRepository 已完備,但我希望前端的 ICustomerService 實際來再切一層 CustomerRepository,然後 CustomerRepository 由 IUnitOfWork 來動態定要使用哪一個 Repository<T> 來取得資料來源,所以我們定義 IUnitOfWork 用以注入到 CustomerRepository 中,UnitOfWork 提供一個可動態取得任何有實做Repository<T> 介面的介面,如 ICustomerRepository、IEmployeeRepository 等。
UnitOfWork 模式的類別圖
接著依照如上類別圖開始撰寫程式碼。
IUnitOfWork.cs
1: public interface IUnitOfWork
2: {
3: void Dispose();
4: IRepository<T> Repository<T>() where T : class;
5: }
接著建立 UnitOfWork.cs 並實作 IUnitOfWork 介面,主要實作重點在 Repository<T> 的方法,這個方法必須可動態依據外部傳入的泛型實體型別動態的產生對應由實作 IRepository<T> 的實體物件。詳細程式碼如下:
UnitOfWork.cs
1: public class UnitOfWork : IUnitOfWork, IDisposable
2: {
3: private DalCusOrders _context { get; set; }
4: private IDbContextFactory _factory;
5:
6: private Hashtable _repositories;
7: private bool _disposed = false;
8:
9: public UnitOfWork(IDbContextFactory factory)
10: {
11: if (factory == null)
12: {
13: throw new ArgumentException("IDbContextFactory is null!.");
14: }
15: this._factory = factory;
16: this._context = factory.GetDbContext();
17: }
18: /// <summary>
19: /// 取得實作 IRepository 的物件.
20: /// </summary>
21: /// <typeparam name="T"></typeparam>
22: /// <returns></returns>
23: public IRepository<T> Repository<T>() where T : class
24: {
25: if (_repositories == null)
26: _repositories = new Hashtable();
27:
28: var type = typeof(T).Name;
29:
30: //判斷 Hashtable 中是否已經擁有符合的 type
31: if (!_repositories.ContainsKey(type))
32: {
33: //建立需要的Repository
34: var repositoryType = typeof(GenericRepository<>);
35:
36: var repositoryInstance =
37: Activator.CreateInstance(
38: repositoryType.MakeGenericType(typeof(T)),
39: _factory);
40:
41: _repositories.Add(type, repositoryInstance);
42: }
43:
44: return (IRepository<T>)_repositories[type];
45: }
46:
47: protected virtual void Dispose(bool disposing)
48: {
49: if (!this._disposed)
50: {
51: if (disposing)
52: {
53: _context.Dispose();
54: }
55: }
56: this._disposed = true;
57: }
58:
59: /// <summary>
60: /// 釋放物件
61: /// </summary>
62: public void Dispose()
63: {
64: Dispose(true);
65: GC.SuppressFinalize(this);
66: }
67: }
會這麼做的原因在於,我希望在 CustomerRepository 的地方,注入的為 IUnitOfWork 介面,透過 IUnitOfWork 介面,使的各個 EmployeeRepository、EmployeeRepository 、OrderRepository、ProductRepository 等,均可動態取得需要的,且實作 IRepository<T> 的物件,這史的各個 Repository 程式碼變得更精簡,也不需要知道 UnitOfWork 的實作為和,且 IUnitOfWork 也會使用 Autofac 進行注入。
所以在 CustomerRepository 筆者撰寫的程式碼會是如下:
1: public class CustomerRepository : ICustomerRepository
2: {
3: private IUnitOfWork _uow;
4:
5: public CustomerRepository(IUnitOfWork uow)
6: {
7: _uow = uow;
8: }
9: /// <summary>
10: /// 取得特定客戶的所有訂單.
11: /// </summary>
12: /// <param name="CusID"></param>
13: /// <returns></returns>
14: public IEnumerable<CusOrders> GetByCusID(string CusID)
15: {
16: return _uow.Repository<Customers>().GetByCusID(CusID);
17: }
18: /// <summary>
19: /// 取得所有的 Customer, ContactName 的連絡資訊.
20: /// </summary>
21: /// <returns></returns>
22: public IEnumerable<Customers> GetCustomerList()
23: {
24: return _uow.Repository<Customers>().GetCustomerList();
25: }
26: /// <summary>
27: /// 使用客戶代碼取得客戶的聯絡人名稱
28: /// </summary>
29: /// <param name="CustomerId"></param>
30: /// <returns></returns>
31: public object GetCustomerByCustomerID(string CustomerId)
32: {
33: return _uow.Repository<Customers>().GetCustomerByCustomerID(CustomerId);
34: }
35:
36: public int Add(DataAccess.Entities.Customers entity)
37: {
38: throw new NotImplementedException();
39: }
40:
41: public int Del<K>(K id)
42: {
43: throw new NotImplementedException();
44: }
45:
46: public int Edit(DataAccess.Entities.Customers entity)
47: {
48: throw new NotImplementedException();
49: }
50:
51: public IQueryable<DataAccess.Entities.Customers> GetAll()
52: {
53: return GetCustomerList().AsQueryable();
54: }
55: }
同樣的,一些空的實作因為沒有使用到,暫且不理會。
最後,調整完畢的 Cus.Models 專案的架構以類別來檢視的話會是如下:
從 Visual Studio 2013 的方案總管檢視的結構會是如下:
E. 修改 Cus.Service 專案,並將各 Service 類別改注入 ICustomerRepository、IEmployeeRepository、IOrderRepository、等等
因為我希望在 CustomerService 的地方,注入的為 ICustomerRepository,且 Service 的介面不見得會與後端的 Repository 提供的介面名稱相同,因為是實際提供外接存取的介面,所以我們得另外撰寫服務 Service 的介面。
註:
這個 Cus.Service 專案可從筆者之前的分享:『投影片分享 - Study4.TW Nov 新竹課程- (架構設計好簡單- 如何快速從Web Form 變成 ASP.NET MVC)』下載的『分層完畢後的 ASP.NET Web Form 與 ASP.NET MVC 4 的應用程式』之中複製出來修改即可。
如下圖解說,以 CustomerService 為範例,GetByCusID() 方法在 Service 中由 ICustomerService 介面決定要提供哪些服務:
以提供訂單服務的 IOrderService 與 OrderService 的程式碼會是如下:
IOrderService.cs
1: public interface IOrderService
2: {
3: int AddOrder(OrderViewModel orders);
4: }
OrderService.cs
1: /// <summary>
2: /// 訂單處理的服務 Service
3: /// </summary>
4: public class OrderService: IOrderService
5: {
6: private IOrderRepository _order = null;
7:
8: public OrderService(IOrderRepository order)
9: {
10: _order = order;
11: }
12: /// <summary>
13: /// 新增一筆訂單資料.
14: /// </summary>
15: /// <param name="orders"></param>
16: /// <returns></returns>
17: public int AddOrder(OrderViewModel orders)
18: {
19: Orders order = new Orders()
20: {
21: CustomerID = orders.CustomerID,
22: EmployeeID = orders.EmployeeID,
23: OrderID = orders.OrderID,
24: Freight = orders.Freight,
25: OrderDate = orders.OrderDate,
26: RequiredDate = orders.RequiredDate,
27: ShipAddress = orders.ShipAddress,
28: ShipCity = orders.ShipCity,
29: ShipCountry = orders.ShipCountry,
30: ShipName = orders.ShipName,
31: ShippedDate = orders.ShippedDate,
32: ShipPostalCode = orders.ShipPostalCode,
33: ShipRegion = orders.ShipRegion,
34: ShipVia = orders.ShipVia
35: };
36: return _order.AddOrder(order);
37: }
38: }
如上,AddOrder 在 Service 端接受的是 ViewModel,所以需要再做一層轉換。
而之後調整過後的 Cus.Service 專案從 Visual Studio 2013 的方案總管檢視會是如下:
與之前的 Cus.Service 專案相比,增加了 Interface 的部分。其他各個介面 & 服務的程式碼與因為作法大同小異,與筆者就不詳細列出了,有興趣的讀者可以下載本篇下方的範例程式連結。
三、調整 Cus.ViewModels 專案
這邊我們對原本在 IRepository 介面中直接參照 Cus.DataAccess.Entities 的相關 Entity 如:Customers 、Employees 等,直接建立對應的 ViewModels,以便直接傳遞資料至 View,調整後的 Cus.ViewModels 專案如下:
四、調整主要 ASP.NET MVC 專案
這裡筆者改以 ASP.NET MVC 5 的專案來 DEMO,所以各位是使用原有的範例程式碼的話,可能得先將該程式碼升級為 ASP.NET MVC 5 的專案。
首先,我們得依照以下步驟來進行。
A. 透過 NuGet 加入 Autofac
B. 加入 AutofacConfig.cs
在 App_Start 的下面加入 AutofacConfig.cs
並撰寫如下內容:
1: using Autofac;
2: using Autofac.Integration.Mvc;
3: using Cus.DataAccess.Entities;
4: using Cus.Models.DbContextFactory;
5: using Cus.Models.Interface;
6: using Cus.Models.Order;
7: using Cus.Models.Repository;
8: using Cus.Services.Interface;
9: using Cus.Services.Order;
10: using System;
11: using System.Collections.Generic;
12: using System.Configuration;
13: using System.Linq;
14: using System.Reflection;
15: using System.Web;
16: using System.Web.Mvc;
17:
18: namespace UseSQLMvc5Application1.App_Start
19: {
20: public class AutofacConfig
21: {
22: /// <summary>
23: ///
24: /// </summary>
25: public static void Bootstrapper()
26: {
27: //容器建立者
28: var builder = new ContainerBuilder();
29: builder.RegisterControllers(Assembly.GetExecutingAssembly());
30:
31: builder.RegisterType<CustomerService>().As<ICustomerService>().InstancePerHttpRequest();
32: builder.RegisterType<EmployeeService>().As<IEmployeeService>().InstancePerHttpRequest();
33: builder.RegisterType<OrderService>().As<IOrderService>().InstancePerHttpRequest();
34: builder.RegisterType<ProductService>().As<IProductService>().InstancePerHttpRequest();
35: builder.RegisterType<ShipperService>().As<IShipperService>().InstancePerHttpRequest();
36:
37: string connectionString = ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString;
38:
39: //註冊DbContextFactory
40: builder.RegisterType<DbContextFactory>()
41: .WithParameter("connectionString", connectionString)
42: .As<IDbContextFactory>()
43: .InstancePerHttpRequest();
44:
45: builder.RegisterType<CustomerRepository>()
46: .WithParameter("IUnitOfWork", new UnitOfWork(new DbContextFactory(connectionString)))
47: .As<ICustomerRepository>().InstancePerHttpRequest();
48:
49: builder.RegisterType<EmployeeRepository>()
50: .WithParameter("IUnitOfWork", new UnitOfWork(new DbContextFactory(connectionString)))
51: .As<IEmployeeRepository>().InstancePerHttpRequest();
52:
53: builder.RegisterType<OrderRepository>()
54: .WithParameter("IUnitOfWork", new UnitOfWork(new DbContextFactory(connectionString)))
55: .As<IOrderRepository>().InstancePerHttpRequest();
56:
57: builder.RegisterType<ProductRepository>()
58: .WithParameter("IUnitOfWork", new UnitOfWork(new DbContextFactory(connectionString)))
59: .As<IProductRepository>().InstancePerHttpRequest();
60:
61: builder.RegisterType<ShipperRepository>()
62: .WithParameter("IUnitOfWork", new UnitOfWork(new DbContextFactory(connectionString)))
63: .As<IShipperRepository>().InstancePerHttpRequest();
64:
65: //註冊 Repository
66: builder.RegisterGeneric(typeof(GenericRepository<>))
67: .WithParameter("IDbContextFactory", new DbContextFactory(connectionString))
68: .As(typeof(IRepository<>));
69:
70: builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerHttpRequest();
71:
72: builder.RegisterFilterProvider();
73:
74: //建立容器
75: IContainer container = builder.Build();
76: DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
77: }
78: }
79: }
如上程式碼其實非常簡單,以 Autofac 來說,需先告訴期有哪些型別 與 介面,筆者先註冊五大 Services 介面,接著註冊 IDbContextFactory 與DbContextFactory ,再來就是註冊各 Repository 實體型態、Constructor 的介面參數為何、要注入的介面為何等。
下簡圖說明 Autofac 註冊的方式:
RegusterType() ==> 要註冊的(實作介面) 的實體型態
WithParameter() ==> Constructor 參數
As() ==> 注入的介面
C. 調整 CusController 取得相關 Service 的方式
調整方式相信讀者看下圖就完全理解了,其他下面的 Controller 程式碼根本也不需要什麼異動,只是改呼叫介面所提供的
最後以元件圖來綜觀整個架構。
五、測試與執行專案
當然執行時,記得將 RegisterRoutes 裡,註冊的 controller 改為 "Cus",才可以首頁一進去就是 CusController。
首頁畫面:
新增訂單:
以上,成功轉換為 IoC、DI 的一個架構,提高了這個專案的可維護姓、擴充性。
結語:
在進行架構設計時,善用一些 Third-Party 的工具或是元件,也可以達到筆者講的:找出最省時、省力、又保有較佳的設計、維護姓、時效性,又兼顧團隊的 Skill、以後維護也方便的一種方式。因為架構設計有時候也是一種全盤的考慮,不光是技術,有時,天、時、地、利、人和、環境,也會是考慮因素之一。
我們下次見
簽名:
學習是一趟奇妙的旅程
這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。
軟體開發之路(FB 社團):https://www.facebook.com/groups/361804473860062/
Gelis 程式設計訓練營(粉絲團):https://www.facebook.com/gelis.dev.learning/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^