I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: Có không "tốt nhất", hay "nhất đúng" cách tiếp cận để xây dựng một kiến trúc ứng dụng. Đó là một công việc rất sáng tạo. Bạn nên luôn luôn chọn kiến trúc đơn giản và có thể mở rộng nhất, điều này sẽ rõ ràng đối với bất kỳ nhà phát triển nào, những người bắt đầu làm việc với dự án của bạn hoặc cho các nhà phát triển khác trong nhóm của bạn, nhưng tôi đồng ý rằng có thể có "tốt" và "xấu" " ngành kiến trúc.
Bạn nói:, collect the most interesting approaches from experienced iOS developers
tôi không nghĩ rằng cách tiếp cận của tôi là thú vị hay đúng đắn nhất, nhưng tôi đã sử dụng nó trong một số dự án và hài lòng với nó. Đó là một cách tiếp cận hỗn hợp của những người bạn đã đề cập ở trên, và cũng với những cải tiến từ những nỗ lực nghiên cứu của riêng tôi. Tôi thú vị trong các vấn đề xây dựng phương pháp tiếp cận, kết hợp một số mô hình và thành ngữ nổi tiếng. Tôi nghĩ rằng rất nhiều mẫu doanh nghiệp của Fowler có thể được áp dụng thành công cho các ứng dụng di động. Dưới đây là danh sách những thứ thú vị nhất mà chúng ta có thể áp dụng để tạo kiến trúc ứng dụng iOS ( theo ý kiến của tôi ): Tầng dịch vụ , Đơn vị công việc , Mặt tiền từ xa , Đối tượng truyền dữ liệu ,Cổng , Lớp siêu lớp , Trường hợp đặc biệt , Mô hình miền . Bạn phải luôn thiết kế chính xác một lớp mô hình và luôn không quên sự kiên trì (nó có thể làm tăng đáng kể hiệu suất của ứng dụng của bạn). Bạn có thể sử dụng Core Data
cho việc này. Nhưng bạn không nên quên, đó Core Data
không phải là ORM hay cơ sở dữ liệu, mà là một trình quản lý biểu đồ đối tượng với sự kiên trì là một lựa chọn tốt của nó. Vì vậy, rất thường xuyên Core Data
có thể quá nặng đối với nhu cầu của bạn và bạn có thể xem xét các giải pháp mới như Realm và Couchbase Lite hoặc xây dựng lớp duy trì / ánh xạ đối tượng nhẹ của riêng bạn, dựa trên SQLite hoặc LevelDB thô. Ngoài ra, tôi khuyên bạn nên làm quen với Thiết kế hướng miền và CQRS .
Lúc đầu, tôi nghĩ, chúng ta nên tạo một lớp khác để kết nối mạng, vì chúng ta không muốn các bộ điều khiển chất béo hoặc các mô hình nặng, quá tải. Tôi không tin vào những fat model, skinny controller
điều đó. Nhưng tôi tin vào skinny everything
cách tiếp cận, bởi vì không có lớp học nào nên béo cả. Tất cả các mạng có thể được trừu tượng hóa như logic kinh doanh, do đó chúng ta nên có một lớp khác, nơi chúng ta có thể đặt nó. Lớp dịch vụ là những gì chúng ta cần:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
Trong MVC
vương quốc của chúng ta Service Layer
là một cái gì đó giống như một trung gian hòa giải giữa mô hình miền và bộ điều khiển. Có một biến thể khá giống nhau của phương pháp này được gọi là MVCS trong đó a Store
thực sự là Service
lớp của chúng tôi . Store
bán các phiên bản mô hình và xử lý kết nối mạng, bộ đệm, v.v. Tôi muốn đề cập rằng bạn không nên viết tất cả logic mạng và kinh doanh trong lớp dịch vụ của mình. Đây cũng có thể được coi là một thiết kế xấu. Để biết thêm thông tin, hãy xem các mô hình miền Thiếu máu và Giàu . Một số phương thức dịch vụ và logic nghiệp vụ có thể được xử lý trong mô hình, vì vậy nó sẽ là mô hình "phong phú" (có hành vi).
Tôi luôn sử dụng rộng rãi hai thư viện: AFNetworking 2.0 và ReactiveCocoa . Tôi nghĩ rằng nó là phải có cho bất kỳ ứng dụng hiện đại nào tương tác với mạng và dịch vụ web hoặc chứa logic UI phức tạp.
NGÀNH KIẾN TRÚC
Đầu tiên tôi tạo một APIClient
lớp chung , đó là một lớp con của AFHTTPSessionManager . Đây là một đặc điểm của tất cả các mạng trong ứng dụng: tất cả các lớp dịch vụ ủy thác các yêu cầu REST thực tế cho nó. Nó chứa tất cả các tùy chỉnh của ứng dụng khách HTTP mà tôi cần trong ứng dụng cụ thể: Ghim SSL, xử lý lỗi và tạo NSError
các đối tượng đơn giản với lý do lỗi và mô tả chi tiết về tất cả API
và lỗi kết nối (trong trường hợp đó, bộ điều khiển sẽ có thể hiển thị thông báo chính xác cho người dùng), cài đặt tuần tự hóa yêu cầu và phản hồi, tiêu đề http và các nội dung liên quan đến mạng khác. Sau đó, tôi một cách logic chia tất cả các yêu cầu API vào subservices hoặc, đúng hơn, microservices : UserSerivces
, CommonServices
, SecurityServices
,FriendsServices
và như vậy, theo logic kinh doanh họ thực hiện. Mỗi microservice này là một lớp riêng biệt. Họ, cùng nhau, tạo thành một Service Layer
. Các lớp này chứa các phương thức cho từng yêu cầu API, xử lý các mô hình miền và luôn trả về a RACSignal
với mô hình phản hồi được phân tích cú pháp hoặc NSError
cho người gọi.
Tôi muốn đề cập rằng nếu bạn có logic tuần tự hóa mô hình phức tạp - thì hãy tạo một lớp khác cho nó: một cái gì đó giống như Data Mapper nhưng tổng quát hơn, ví dụ JSON / XML -> Model mapper. Nếu bạn có bộ đệm: thì cũng tạo nó dưới dạng một lớp / dịch vụ riêng biệt (bạn không nên trộn logic nghiệp vụ với bộ đệm). Tại sao? Bởi vì lớp bộ nhớ đệm chính xác có thể khá phức tạp với các vấn đề riêng của nó. Mọi người thực hiện logic phức tạp để có được bộ nhớ đệm hợp lệ, có thể dự đoán được, ví dụ như bộ nhớ đệm đơn hình với các phép chiếu dựa trên các profunctor. Bạn có thể đọc về thư viện xinh đẹp này có tên Carlos để hiểu thêm. Và đừng quên rằng Core Data thực sự có thể giúp bạn trong mọi vấn đề về bộ nhớ đệm và sẽ cho phép bạn viết ít logic hơn. Ngoài ra, nếu bạn có một số logic giữa NSManagedObjectContext
và các mô hình yêu cầu máy chủ, bạn có thể sử dụngMẫu kho lưu trữ , phân tách logic lấy dữ liệu và ánh xạ nó tới mô hình thực thể từ logic nghiệp vụ hoạt động trên mô hình. Vì vậy, tôi khuyên bạn nên sử dụng mẫu Kho lưu trữ ngay cả khi bạn có kiến trúc dựa trên Dữ liệu lõi. Kho lon những thứ trừu tượng, giống như NSFetchRequest
, NSEntityDescription
, NSPredicate
và vân vân với các phương pháp đơn giản như get
hay put
.
Sau tất cả các hành động này trong lớp Dịch vụ, trình gọi (trình điều khiển xem) có thể thực hiện một số nội dung không đồng bộ phức tạp với phản hồi: thao tác tín hiệu, xâu chuỗi, ánh xạ, v.v. với sự trợ giúp của ReactiveCocoa
nguyên thủy hoặc chỉ đăng ký vào nó và hiển thị kết quả trong chế độ xem . Tôi tiêm với dependency injection trong tất cả các lớp dịch vụ của tôi APIClient
, mà sẽ dịch một cuộc gọi dịch vụ cụ thể vào tương ứng GET
, POST
, PUT
, DELETE
vv yêu cầu đến thiết bị đầu cuối REST. Trong trường hợp APIClient
này được truyền hoàn toàn cho tất cả các bộ điều khiển, bạn có thể làm cho điều này rõ ràng với một tham số trên APIClient
các lớp dịch vụ. Điều này có thể có ý nghĩa nếu bạn muốn sử dụng các tùy chỉnh khác nhau củaAPIClient
đối với các lớp dịch vụ cụ thể, nhưng nếu bạn, vì một số lý do, không muốn có thêm bản sao hoặc bạn chắc chắn rằng bạn sẽ luôn sử dụng một phiên bản cụ thể (không có tùy chỉnh) của APIClient
- hãy biến nó thành một singleton, nhưng KHÔNG, xin vui lòng 'T làm cho các lớp dịch vụ như singletons.
Sau đó, mỗi bộ điều khiển khung nhìn lại với DI tiêm lớp dịch vụ mà nó cần, gọi các phương thức dịch vụ phù hợp và tổng hợp kết quả của chúng với logic UI. Đối với tiêm phụ thuộc, tôi thích sử dụng BloodMagic hoặc Typhoon khung mạnh hơn . Tôi không bao giờ sử dụng singletons, APIManagerWhatever
lớp Chúa hoặc những thứ sai trái khác. Bởi vì nếu bạn gọi lớp của bạn WhateverManager
, điều này cho thấy bạn không biết mục đích của nó và đó là một lựa chọn thiết kế tồi . Singletons cũng là một mô hình chống, và trong hầu hết các trường hợp (trừ những trường hợp hiếm) là một giải pháp sai . Singleton chỉ nên được xem xét nếu cả ba tiêu chí sau đây được thỏa mãn:
- Quyền sở hữu của cá thể đơn lẻ có thể được chỉ định một cách hợp lý;
- Khởi tạo lười biếng là mong muốn;
- Truy cập toàn cầu không được quy định khác.
Trong trường hợp của chúng tôi, quyền sở hữu đối với một trường hợp duy nhất không phải là vấn đề và chúng tôi cũng không cần quyền truy cập toàn cầu sau khi chúng tôi chia người quản lý thần của mình thành các dịch vụ, vì hiện tại chỉ có một hoặc một số bộ điều khiển chuyên dụng cần một dịch vụ cụ thể (ví dụ: UserProfile
nhu cầu của bộ điều khiển UserServices
, v.v.) .
Chúng ta nên luôn luôn tôn trọng S
nguyên tắc trong RẮN và sử dụng các mối quan tâm riêng biệt , vì vậy đừng đặt tất cả các phương thức dịch vụ và cuộc gọi mạng của bạn vào một lớp, vì điều đó thật điên rồ, đặc biệt là nếu bạn phát triển một ứng dụng doanh nghiệp lớn. Đó là lý do tại sao chúng ta nên xem xét phương pháp tiêm phụ thuộc và dịch vụ. Tôi coi cách tiếp cận này là hiện đại và hậu OO . Trong trường hợp này, chúng tôi chia ứng dụng của chúng tôi thành hai phần: logic điều khiển (bộ điều khiển và sự kiện) và tham số.
Một loại tham số sẽ là thông số dữ liệu thông thường của người dùng. Đó là những gì chúng ta chuyển qua các hàm, thao tác, sửa đổi, tồn tại, v.v. Đây là các thực thể, tập hợp, tập hợp, lớp tình huống. Các loại khác sẽ là các dịch vụ khác Đây là các lớp đóng gói logic nghiệp vụ, cho phép giao tiếp với các hệ thống bên ngoài, cung cấp quyền truy cập dữ liệu.
Dưới đây là một quy trình làm việc chung của kiến trúc của tôi. Giả sử chúng ta có một FriendsViewController
, hiển thị danh sách bạn bè của người dùng và chúng ta có một tùy chọn để xóa khỏi bạn bè. Tôi tạo một phương thức trong FriendsServices
lớp gọi là:
- (RACSignal *)removeFriend:(Friend * const)friend
trong đó Friend
một đối tượng mô hình / miền (hoặc nó có thể chỉ là một User
đối tượng nếu chúng có các thuộc tính tương tự). Underhood phương pháp này phân tích Friend
để NSDictionary
các thông số JSON friend_id
, name
, surname
, friend_request_id
và vân vân. Tôi luôn sử dụng thư viện Mantle cho kiểu soạn sẵn này và cho lớp mô hình của mình (phân tích ngược và chuyển tiếp, quản lý phân cấp đối tượng lồng nhau trong JSON, v.v.). Sau khi phân tích nó gọi APIClient
DELETE
phương pháp để thực hiện một yêu cầu REST thực tế và lợi nhuận Response
trong RACSignal
cho người gọi ( FriendsViewController
trong trường hợp của chúng tôi) để hiển thị thông báo thích hợp cho người sử dụng hay bất cứ điều gì.
Nếu ứng dụng của chúng tôi là một ứng dụng rất lớn, chúng tôi phải phân tách logic của chúng tôi thậm chí rõ ràng hơn. Ví dụ, không phải lúc nào cũng tốt để trộn Repository
hoặc mô hình logic với Service
một. Khi tôi mô tả cách tiếp cận của mình, tôi đã nói rằng removeFriend
phương thức đó nên nằm trong Service
lớp, nhưng nếu chúng ta sẽ có tính mô phạm hơn, chúng ta có thể nhận thấy rằng nó tốt hơn thuộc về nó Repository
. Chúng ta hãy nhớ Kho lưu trữ là gì. Eric Evans đã cho nó một mô tả chính xác trong cuốn sách của mình [DDD]:
Kho lưu trữ đại diện cho tất cả các đối tượng của một loại nhất định dưới dạng một tập hợp khái niệm. Nó hoạt động như một bộ sưu tập, ngoại trừ với khả năng truy vấn phức tạp hơn.
Vì vậy, a Repository
về cơ bản là một mặt tiền sử dụng ngữ nghĩa kiểu Bộ sưu tập (Thêm, Cập nhật, Xóa) để cung cấp quyền truy cập vào dữ liệu / đối tượng. Đó là lý do tại sao khi bạn có một cái gì đó như: getFriendsList
, getUserGroups
, removeFriend
bạn có thể đặt nó trong Repository
, bởi vì bộ sưu tập giống như ngữ nghĩa được khá rõ ràng ở đây. Và mã như:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
chắc chắn là một logic nghiệp vụ, bởi vì nó nằm ngoài các CRUD
hoạt động cơ bản và kết nối hai đối tượng miền ( Friend
và Request
), đó là lý do tại sao nó nên được đặt trong Service
lớp. Ngoài ra tôi muốn thông báo: không tạo ra sự trừu tượng không cần thiết . Sử dụng tất cả các phương pháp tiếp cận một cách khôn ngoan. Bởi vì nếu bạn sẽ áp đảo ứng dụng của mình bằng sự trừu tượng, điều này sẽ làm tăng độ phức tạp ngẫu nhiên của nó và sự phức tạp gây ra nhiều vấn đề trong hệ thống phần mềm hơn bất kỳ thứ gì khác
Tôi mô tả cho bạn một ví dụ Objective-C "cũ" nhưng cách tiếp cận này có thể được điều chỉnh rất dễ dàng cho ngôn ngữ Swift với nhiều cải tiến hơn, bởi vì nó có nhiều tính năng hữu ích hơn và đường chức năng. Tôi rất khuyên bạn nên sử dụng thư viện này: Moya . Nó cho phép bạn tạo một APIClient
lớp thanh lịch hơn (công việc của chúng tôi như bạn nhớ). Bây giờ, APIClient
nhà cung cấp của chúng tôi sẽ là một loại giá trị (enum) với các phần mở rộng tuân thủ các giao thức và tận dụng việc khớp mẫu phá hủy. Swift enums + khớp mẫu cho phép chúng ta tạo các kiểu dữ liệu đại số như trong lập trình hàm cổ điển. Các dịch vụ siêu nhỏ của chúng tôi sẽ sử dụng APIClient
nhà cung cấp được cải tiến này như trong cách tiếp cận Objective-C thông thường. Đối với lớp mô hình thay vì Mantle
bạn có thể sử dụng thư viện ObjectMapperhoặc tôi thích sử dụng thư viện Argo thanh lịch và chức năng hơn .
Vì vậy, tôi đã mô tả phương pháp kiến trúc chung của tôi, có thể được điều chỉnh cho bất kỳ ứng dụng nào, tôi nghĩ vậy. Có thể có nhiều cải tiến hơn, tất nhiên. Tôi khuyên bạn nên học lập trình chức năng, bởi vì bạn có thể hưởng lợi từ nó rất nhiều, nhưng đừng đi quá xa với nó. Loại bỏ trạng thái đột biến, chia sẻ, toàn cầu quá mức, tạo ra một mô hình miền bất biến hoặc tạo các hàm thuần túy mà không có tác dụng phụ bên ngoài, nói chung, là một thực tiễn tốt và Swift
ngôn ngữ mới khuyến khích điều này. Nhưng hãy luôn nhớ rằng, quá tải mã của bạn với các mẫu chức năng thuần túy nặng nề, các cách tiếp cận lý thuyết danh mục là một ý tưởng tồi , bởi vì các nhà phát triển khác sẽ đọc và hỗ trợ mã của bạn, và họ có thể thất vọng hoặc đáng sợ vềprismatic profunctors
và những thứ như vậy trong mô hình bất biến của bạn. Điều tương tự với ReactiveCocoa
: đừng RACify
mã của bạn quá nhiều , bởi vì nó có thể trở nên không thể đọc được rất nhanh, đặc biệt là đối với người mới. Sử dụng nó khi nó thực sự có thể đơn giản hóa các mục tiêu và logic của bạn.
Vì vậy, read a lot, mix, experiment, and try to pick up the best from different architectural approaches
. Đó là lời khuyên tốt nhất tôi có thể cung cấp cho bạn.