Copy on Write (CoW) là 1 khái niệm ko hề mới trong Swift. Nó đã được Apple giới thiệu trong WWDC 2015 và được áp dụng từ iOS 7.0. Tuy nhiên thực chất CoW là gì và chúng có tác dụng gì? Hãy cùng tìm hiểu trong bài viết này nhé.
Table of contents
Copy on Write là gì
Kết luận
References
Copy on Write là gì?
Trong Swift, chúng ta có các kiểu Reference type và Value type. Nếu bạn gán một value type cho một biến hoặc pass nó như một parameter của function (không phải parameter kiểu inout) thì dữ liệu của value type này sẽ được copy. Lúc này, ta sẽ có hai value type có nội dung giống nhau nhưng trỏ đến hai địa chỉ bộ nhớ riêng biệt. Hôm nay ta sẽ bàn về Copy on Write – một cơ chế quan trọng trong việc tối ưu bộ nhớ của Swift.
Trong Swift, khi bạn có một khối lượng lớn các value type và muốn gán hoặc truyền chúng qua các function, nếu bạn copy tất cả dữ liệu sang một vị trí khác trong bộ nhớ thì sẽ gây ra hiện tượng lãng phí hiệu năng. Để giảm thiểu tình trạng này, Swift đã triển khai cơ chế Copy on Write cho một số kiểu dữ liệu là value type như array, dictionary,…
Hiểu 1 cách đơn giản, nếu bạn có 1 array có 1000 phần tử và bạn muốn copy mảng đó vào 1 biến khác, Swift sẽ không sao chép ngay lập tức cả 1000 phần tử này mà sẽ sử dụng đến cơ chế Copy on Write: Khi bạn trỏ 2 biến vào cùng 1 mảng, chúng đều trỏ vào cùng 1 địa chỉ ô nhớ, và chỉ đến khi bạn sửa đổi 1 trong 2 biến đó, swift mới tạo ra 1 bản copy mới để sửa và chỉ sửa trên bản copy đó và vẫn giữ nguyên biến còn lại. Bằng cách trì hoãn việc sao chép dữ liệu cho đến khi thực sự cần thiết, Swift đã đảm bảo được việc tối ưu được performance của hệ thống
Copy on Write ko phải là cơ chế mặc định cho tất cả các kiểu value type, mà chỉ được áp dụng cho 1 số kiểu như Aray, Collections,… Ngoài ra, với những kiểu value type mà bạn tự custom thì cũng ko có sẵn cơ chế này mà phải tự implement thêm.
Ví dụ về cách hoạt động của Copy on Write
import Foundation
func print(address o: UnsafeRawPointer ) {
print(String(format: "%p", Int(bitPattern: o)))
}
var array1: [Int] = [0, 1, 2, 3]
var array2 = array1
//Print with just assign
print(address: array1) //0x600000078de0
print(address: array2) //0x600000078de0
//Let's mutate array2 to see what's
array2.append(4)
print(address: array2) //0x6000000aa100
//Output
//0x600000078de0 array1 address
//0x600000078de0 array2 address before mutation
//0x6000000aa100 array2 address after mutation
Đây là 1 ví dụ đơn giản để chỉ cách hoạt động của Copy on Write. Trước hết, tạo biến array1 rồi sau đó gán aray2 bằng với array1. Khi chưa thực hiện thay đổi giá trị thì array2 vẫn trỏ vào cùng 1 địa chỉ ô nhớ với array1. Chỉ khi ta thay đổi giá trị của array2 thì nó mới được copy sang 1 địa chỉ ô nhớ khác, và giá trị mới sẽ trỏ vào địa chỉ ô nhớ này, còn array1 sẽ không có sự thay đổi gì.
Implement cơ chế Copy on Write cho các dạng value type tự tạo
Bạn có thể tự mình implement cơ chế Copy on Write cho các kiểu dữ liệu mà bạn tự custom. Đây là ví dụ trên OptimizationTips.rst trong repo chính của Swift
final class Ref<T> {
var val : T
init(_ v : T) {val = v}
}
struct Box<T> {
var ref : Ref<T>
init(_ x : T) { ref = Ref(x) }
var value: T {
get { return ref.val }
set {
if (!isUniquelyReferencedNonObjC(&ref)) {
ref = Ref(newValue)
return
}
ref.val = newValue
}
}
}
// This code was an example taken from the swift repo doc file OptimizationTips
// Link: https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst#advice-use-copy-on-write-semantics-for-large-values
Đoạn code trên sử dụng loại reference type để triển khai cho kiểu giá trị dạng generics. Về cơ bản, đây là 1 warrper quản lý loại reference type và chỉ trả về 1 instance mới nếu giá trị được tham chiếu không là duy nhất. Nếu không, nó chỉ thay đổi giá trị của kiểu tham chiếu.
Kết luận:
Copy on Write là 1 cơ chế rất thông minh để tối ưu hoá việc copy giá trị của các kiểu value type. Đây là 1 cơ chế được sử dụng rất nhiều Swift, dù hầu như chúng ta ko nhìn thấy nó 1 cách rõ ràng vì chúng đã được thực hiện trên các thư viện chuẩn của Swift. Nhưng chúng ta nên biết để có thể tận dụng tối đa lợi ích mà Copy on Write mang lại.
Chào mọi người. Bài viết trước mình đã giới thiệu về Android Library và cách publish lên remote (cụ thể ở đây là Jitpack.io)
Tiếp nối chuỗi bài liên quan đến Library này.
Hôm nay mình đưa ra một tình huống. Khi bạn muốn thay đổi (add, update, fix, …) thứ gì đó trên Library.
Sau khi thay đổi source xong, theo thứ tự bạn phải update version của nó -> tạo bản release trên git -> send chúng lên JitPack -> Mong đợi chúng không có lỗi gì.
Tiếp đến là bên Project chính, các bạn update version của Library trên Project chính và tiếp đó check nó chạy ổn không.
Các bước này sẽ tiếp tục lặp lại nếu như source mà bạn code trong Lib của bạn bị fail.
Tình huống trên gây ra sự mất time và sự chờ đợi. Để giải quyết bài toán trên mình thấy có một giải pháp đó là Local Maven Repository.
Tất nhiên Lib của bạn thường chứa Sample App để sử dụng tất cả các tính năng của Library, vì vậy bạn có thể kiểm tra xem nó có hoạt động hay không mà không cần toàn bộ quá trình mình liệt kê ở trên, nhưng đôi khi vẫn chưa đủ và bạn cần phải kiểm tra trên dự án mà bạn thật sự muốn triển khai Library trên đó.
Những lợi ích mà Local Maven Repository có thể thấy được là:
Đưa cho bạn 1 lựa chọn nếu bạn chỉ muốn lưu Android Library hoặc module dưới local như là một maven repository trong máy tính của bạn. Từ đó bạn có import dependency một cách trực tiếp vào Project của bạn như thể nó đã từng được publish.
Đưa ra giải pháp phù hợp để tiết kiệm về mặt thời gian, tránh sự chờ đợi không cần thiết, cũng như tạo ra sự chủ động cho Developer trong quá trình phát triển.
Nào chúng ta cùng đi vào các bước để triển khai.
Đầu tiên như thiết lập 2 project mà chúng ta đã setup trước đó: CalculatedApp (Main Project) and CalculatedLib (Lib)
Library
Trong file build.gradle, thêm plugin
Định nghĩa artifactId và groupId
Add Config
Nếu Lib của bạn là single module, hoặc là có nhiều modules nhưng các modules không có sự phụ thuộc lẫn nhau.
Nếu Project của bạn có nhiều module là các thư viện độc lập và một module là là tập hợp của các lib đó.
Tip: Nếu project của bạn chứa nhiều modules. Để config chúng, chúng ta phải tạo cho mỗi một Lib một config giống như trên. Để đơn giản hơn chúng ta có thể tạo 1 file publish_local.gradle file. Trong file này mình cài đặt trong một config chung cho các Lib.
Trong mỗi lib mình chỉ cần set-up groupId và artifactId tương ứng
Đặc biệt là add link dẫn đến file config tổng
Publish To Maven Local
Tất cả những gì cần lúc này là run task: publishToMavenLocal
Cuối cùng là Add dependency và config vào Project chính sử dụng thư viện của bạn
Enable mavenLocal() repository vào file Build Systems
Chú ý là add mavenLocal() vào đầu danh sách. Cần lưu ý rằng việc có mavenLocal ở đầu danh sách sẽ giúp bạn luôn chọn các thư viện có sẵn trong thư mục ~/ .m2 / repository / trước tiên
Tiếp Theo, Add dependency tương ứng vào Project chính và sử dụng functions Library trong source code chính.
Vâng. Đó là chia sẻ nhỏ của mình liên quan đến Library, publish remote vs local của chúng.
Các bạn có thể tham khảo source code mình để bên dưới.
Mong rằng bài viết của mình đâu đấy sẽ giúp các bạn trong cộng đồng Android GST mình. Hẹn gặp lại trong bài viết sắp tới.
Lập luận của đối thủ khiến tôi nhớ đến một kẻ ngoại đạo, người, khi được hỏi về thế giới đang đứng trên cái gfi, đã trả lời: "Trên một con rùa." Nhưng con rùa đứng trên cái gì? "Trên một con rùa khác."
—Joseph Barker (1854)
Vì vậy, bạn biết microservices là gì và hy vọng hiểu được những lợi ích chính của chúng. Bây giờ có lẽ bạn đang háo hức muốn đi và bắt đầu tạo ra chúng, phải không? Nhưng bắt đầu từ đâu? Trong chương này, chúng ta sẽ xem xét cách suy nghĩ về ranh giới của các microservice của bạn, hy vọng sẽ tối đa hóa những ưu điểm và tránh một số nhược điểm tiềm ẩn. Nhưng trước tiên, chúng ta cần một cái gì đó để làm việc cùng nó.
Giới thiệu về MusicCorp
Sách về ý tưởng hoạt động tốt hơn với các ví dụ. Nếu có thể, tôi sẽ chia sẻ những câu chuyện từ các tình huống thực tế, nhưng tôi nhận thấy rằng việc có một lĩnh vực hư cấu để hoạt động cũng rất hữu ích. Xuyên suốt cuốn sách, chúng ta sẽ quay trở lại lĩnh vực này, xem khái niệm microservices hoạt động như thế nào trong thế giới này.
Vì vậy, chúng ta hãy chuyển sự chú ý của chúng ta đến một trong những nhà bán lẻ trực tuyến tiên tiến nhất hiện nay – MusicCorp. MusicCorp gần đây là một nhà bán lẻ truyền thống, nhưng sau khi công ty từ bỏ mảng kinh doanh máy hát, mảng kinh doanh nền tảng của MusicCorp, họ ngày càng tập trung nhiều hơn vào việc kinh doanh trực tuyến. Công ty có một trang web, nhưng cảm thấy rằng bây giờ là lúc để tăng gấp đôi doanh số trên các nền tảng trực tuyến. Xét cho cùng, những chiếc iPod đó chỉ là một mốt nhất thời (rõ ràng là Zunes tốt hơn nhiều) và những người hâm mộ âm nhạc đang khá vui khi chờ đĩa CD được chuyển tới nhà của họ. Chất lượng bao giờ cũng hơn sự tiện lợi, phải không? Và trong khi chúng ta đang ở đó, điều mà mọi người vẫn tiếp tục về Spotify này là gì — một nền tảng hướng đến đối tượng thanh thiếu niên?
Mặc dù đi sau một chút so với phần còn lai, nhưng MusicCorp lại có tham vọng lớn. May mắn thay, họ đã quyết định rằng cơ hội tốt nhất để chiếm lấy thị phần là đảm bảo rằng nó có thể thực hiện những thay đổi dễ dàng nhất có thể. Microservices giành chiến thắng!
Điều gì tạo nên một dịch vụ tốt?
Trước khi nhóm từ MusicCorp thay đổi chính mình, tạo ra dịch vụ này đến dịch vụ khác trong nỗ lực cung cấp loại băng 8 bản nhạc cho tất cả mọi người và một vài thứ khác, hãy bắt đầu và nói một chút về ý tưởng cơ bản quan trọng nhất mà chúng ta cần ghi nhớ. Điều gì tạo nên một dịch vụ tốt? Nếu bạn vẫn sống sót sau một lần deploy SOA không thành công, bạn có thể có một số ý tưởng về nơi tiếp theo mà tôi nói đến. Nhưng đề phòng trong trường hợp bạn không phải là kẻ may mắn hoặc giả sử bạn chính là kẻ may mắn, tôi muốn bạn tập trung vào hai khái niệm chính: liên kết lỏng lẻo và tính liên kết cao (loose coupling and high cohesion). Chúng tôi sẽ nói chi tiết trong suốt cuốn sách về những ý tưởng và thực tiễn khác, nhưng chúng đều vô ích nếu chúng tôi làm sai hai điều này.
Mặc dù thực tế là hai thuật ngữ này được sử dụng rất nhiều, đặc biệt là trong ngữ cảnh của các hệ thống hướng đối tượng, nhưng điều đáng bàn về ý nghĩa của chúng đối với microservices là gì.
Liên kết lỏng lẻo
Khi các dịch vụ được kết hợp một cách lỏng lẻo, một sự thay đổi đối với một dịch vụ không yêu cầu thay đổi một dịch vụ khác. Toàn bộ quan điểm của microservice là có thể thực hiện thay đổi đối với một dịch vụ và deploy nó mà không cần thay đổi bất kỳ phần nào khác của hệ thống. Điều này thực sự khá quan trọng.
Những thứ gì gây ra sự kết hợp chặt chẽ? Một sai lầm cổ điển là chọn một phong cách tích hợp liên kết chặt chẽ giữa dịch vụ này với dịch vụ khác, khiến những thay đổi bên trong dịch vụ đòi hỏi nhưng nơi sử dụng nó phải thay đổi. Chúng ta sẽ thảo luận sâu hơn về cách tránh điều này trong Chương 4.
Một dịch vụ được kết hợp một cách lỏng lẻo cần biết rất ít về những dịch vụ mà nó cộng tác cùng. Điều này cũng có nghĩa là chúng tôi có thể muốn giới hạn số lượng các loại yêu cầu/gọi chức năng khác nhau từ dịch vụ này sang dịch vụ khác, bởi vì ngoài vấn đề tiềm tàng về hiệu suất, giao tiếp kiểu này có thể dẫn đến kết nối chặt chẽ.
Độ gắn kết cao
Chúng tôi muốn hành vi liên quan thi đi cùng nhau và hành vi không liên quan nên ở nơi khác. Tại sao? Vâng, nếu chúng ta muốn thay đổi hành vi, chúng ta muốn có thể thay đổi nó ở một nơi và giải phóng thay đổi đó càng sớm càng tốt. Nếu chúng tôi phải thay đổi hành vi đó ở nhiều nơi khác nhau, chúng tôi sẽ phải release nhiều dịch vụ khác nhau (có thể cùng một lúc) để thực hiện thay đổi đó. Việc thực hiện các thay đổi ở nhiều nơi khác nhau sẽ chậm hơn và việc deploy nhiều dịch vụ cùng một lúc là rất rủi ro — cả hai điều này chúng tôi đều muốn tránh.
Vì vậy, chúng tôi muốn tìm các ranh giới của các vấn đề trong lĩnh vực của mình để giúp đảm bảo rằng hành vi liên quan sẽ ở cùng một nơi và giao tiếp với các ranh giới khác một cách lỏng lẻo nhất có thể.
Bối cảnh có giới hạn
Cuốn sách của Eric Evans về Thiết kế theo hướng lĩnh vực – Domain-Driven Design (Addison-Wesley) tập trung vào cách tạo hệ thống mô hình hóa các lĩnh vực trong thế giới thực. Cuốn sách chứa đầy những ý tưởng tuyệt vời như sử dụng ngôn ngữ phổ biến, sự trừu tượng của kho lưu trữ, và những thứ tương tự, nhưng có một khái niệm rất quan trọng mà Evans giới thiệu lúc đầu đã hoàn toàn lướt qua tôi: bối cảnh có giới hạn. Ý tưởng ở đây là bất kỳ lĩnh vực cụ thể nào đều bao gồm nhiều bối cảnh có giới hạn và nằm trong mỗi bối cảnh là những thứ (Eric sử dụng mô hình – model từ rất nhiều, có lẽ tốt hơn là những thứ – things) không cần giao tiếp với bên ngoài cũng như những thứ được chia sẻ ra bên ngoài với các bối cảnh có giới hạn khác. Mỗi bối cảnh có giới hạn có một giao diện rõ ràng, nơi nó quyết định những mô hình nào sẽ chia sẻ với các bối cảnh khác.
Một định nghĩa khác về bối cảnh có giới hạn mà tôi thích là "một trách nhiệm cụ thể được thực thi bởi các ranh giới rõ ràng." Nếu bạn muốn thông tin từ bối cảnh có giới hạn hoặc muốn đưa ra các yêu cầu về chức năng trong bối cảnh có giới hạn, bạn giao tiếp với các ranh giới rõ ràng của nó bằng cách sử dụng các mô hình. Trong cuốn sách của mình, Evans sử dụng một định nghĩa tương tự của các tế bào, nơi mà "[c]ells (tế bào) có thể tồn tại bởi vì màng của chúng xác định những gì ra/vào và xác định những gì có thể đi qua."
Hãy quay lại một chút với công việc kinh doanh của MusicCorp. Lĩnh vực của chúng tôi là toàn bộ hoạt động kinh doanh mà chúng tôi có. Nó bao gồm tất cả mọi thứ từ nhà kho đến bàn tiếp tân, từ tài chính đến đặt hàng. Chúng tôi có thể hoặc không thể mô hình hóa tất cả những điều đó trong phần mềm của mình, nhưng đó vẫn là thứ mà chúng tôi đang điều hành. Chúng ta hãy nghĩ về các phần của lĩnh vực đó trông giống như bối cảnh có giới hạn mà Evans đề cập đến. Tại MusicCorp, kho hàng của chúng tôi là một tổ hợp nhiều hoạt động — quản lý các đơn hàng được chuyển đi (và trả lại), nhận hàng mới, tổ chức các cuộc đua xe nâng, v.v. Ở những nơi khác, bộ phận tài chính có lẽ ít thú vị hơn, nhưng vẫn có một chức năng rất quan trọng trong tổ chức của chúng tôi. Những nhân viên này quản lý bảng lương, giữ các tài khoản của công ty và đưa ra các báo cáo quan trọng. Rất nhiều báo cáo. Chúng có lẽ cũng có những món đồ chơi trên bàn thú vị.
Các mô hình được chia sẻ và mô hình ẩn
Đối với MusicCorp, chúng ta có thể coi bộ phận tài chính và kho hàng là hai bối cảnh có giới hạn riêng biệt. Cả hai đều có giao diện rõ ràng với thế giới bên ngoài (về báo cáo hàng tồn kho, phiếu thanh toán, v.v.) và chúng có thông tin chi tiết mà chỉ họ cần biết (xe nâng, máy tính).
Giờ đây, bộ phận tài chính không cần biết về hoạt động chi tiết bên trong của nhà kho. Tuy nhiên, nó cần phải biết một số điều — ví dụ như nó cần biết về lượng hàng tồn kho để giữ cho các tài khoản được cập nhật. Hình 3-1 cho thấy một ví dụ về sơ đồ bối cảnh (context diagram). Chúng tôi thấy các khái niệm nội bộ của nhà kho, như Người chọn (người chọn đơn hàng), giá đang chứa hàng hóa, v.v. Tương tự như vậy, sổ cái tổng của công ty là không thể thiếu đối với tài chính nhưng không được chia sẻ ra bên ngoài ở đây.
Hình 3-1. Mô hình chia sẻ giữa bộ phận tài chính và kho hàng
Tuy nhiên, để có thể xác định giá trị của công ty, các nhân viên tài chính cần thông tin về cổ phiếu mà chúng tôi nắm giữ. Mặt hàng tồn kho sau đó trở thành một mô hình được chia sẻ giữa hai bối cảnh. Tuy nhiên, lưu ý rằng chúng ta không cần phải tiết lộ mọi thứ về mặt hàng trong kho từ bối cảnh kho hàng một cách mù quáng. Ví dụ: mặc dù nội bộ chúng tôi lưu giữ hồ sơ về một mặt hàng trong kho về nơi nó sẽ được đặt trong nhà kho, nhưng mặt hàng đó không cần phải được tiết lộ thông tin đó trong mô hình dùng chung. Vì vậy, sẽ có sự phân biệt giữa đại diện chỉ bên trong và đại diện bên ngoài mà chúng tôi đưa ra. Theo nhiều cách, điều này báo trước về cuộc thảo luận xung quanh REST trong Chương 4.
Đôi khi chúng ta có thể bắt gặp các mô hình có cùng tên nhưng có ý nghĩa rất khác nhau trong các bối cảnh khác nhau. Ví dụ: chúng ta có thể có khái niệm trả lại, đại diện cho việc khách hàng gửi lại thứ gì đó. Trong bối cảnh của khách hàng, trả lại là tất cả những gì liên quan đến việc in nhãn vận chuyển, gửi một gói hàng và chờ hoàn lại tiền. Đối với nhà kho, điều này có thể đại diện cho một gói hàng sắp được chuyển đến và một mặt hàng trong kho cần được bổ sung. Sau đó, trong kho chúng tôi lưu trữ thông tin bổ sung liên quan đến việc trả lại liên quan đến các nhiệm vụ sẽ được thực hiện; ví dụ: chúng tôi có thể tạo một yêu cầu khôi phục lại. Kết quả của mô hình chia sẻ là các quy trình khác nhau sẽ trở nên liên kết và hỗ trợ các thực thể trong mỗi bối cảnh có giới hạn, nhưng đó là mối quan tâm nội bộ trong chính bối cảnh đó.
Mô-đun và Dịch vụ
Bằng cách suy nghĩ rõ ràng về những mô hình nào nên được chia sẻ và không chia sẻ các đại diện nội bộ của chúng tôi, chúng tôi tránh được một trong những cạm bẫy tiềm ẩn có thể dẫn đến kết hợp chặt chẽ (ngược lại với những gì chúng tôi muốn). Chúng tôi cũng đã xác định một ranh giới trong lĩnh vực của chúng tôi, nơi tất cả các khả năng kinh doanh có cùng chí hướng sẽ tồn tại, mang lại cho chúng tôi sự gắn kết cao mà chúng tôi mong muốn. Những bối cảnh có giới hạn này, sau đó, tự cho mình là những ranh giới sáng tác cực kỳ tốt.
Như chúng ta đã thảo luận trong Chương 1, chúng ta có tùy chọn sử dụng các mô-đun trong một ranh giới quy trình để giữ các đoạn mã nguồn liên quan lại với nhau và cố gắng giảm sự ghép nối với các mô-đun khác trong hệ thống. Khi bạn bắt đầu trên một mã nguồn cơ sở mới, đây có là một khởi đầu tốt. Vì vậy, khi bạn đã tìm thấy các bối cảnh trong lĩnh vực của mình, hãy đảm bảo rằng chúng được mô hình hóa trong mã nguồn cơ sở của bạn dưới dạng mô-đun, với các mô hình được chia sẻ và ẩn.
Các ranh giới mô-đun này sau đó trở thành ứng viên xuất sắc cho các microservice. Nói chung, microservices cần phù hợp trong bối cảnh nhất định. Một khi bạn đã trở nên rất thành thạo, bạn có thể quyết định bỏ qua bước giữ cho bối cảnh có giới hạn được sử dụng như một mô-đun trong một hệ thống monolithic hơn và chuyển thẳng sang tạo ra một dịch vụ riêng biệt. Tuy nhiên, khi bắt đầu, hãy giữ một hệ thống mới ở khía cạnh gần giống monolithic hơn; việc sai lầm trong việc nhìn nhận ranh giới của các dịch vụ có thể gây hậu quả tốn kém, vì vậy hãy chờ đợi mọi thứ ổn định cho đến khi khi bạn nắm bắt được lĩnh vực mới, đó một là điều hợp lý. Chúng ta sẽ thảo luận thêm về vấn đề này trong Chương 5, cùng với các kỹ thuật giúp tách các hệ thống hiện có thành các microservice.
Vì vậy, nếu các ranh giới dịch vụ của chúng tôi phù hợp với các bối cảnh được giới hạn trong lĩnh vực của chúng tôi và microservices của chúng tôi đại diện cho các bối cảnh, chúng tôi đã có một khởi đầu tuyệt vời trong việc đảm bảo rằng các microservice của chúng tôi được liên kết lỏng lẻo và gắn kết chặt chẽ.
Phân rã sớm
Tại ThoughtWorks, bản thân chúng tôi đã trải qua những thách thức khi cố gắng phân chia các microservice quá nhanh. Bên cạnh việc tư vấn, chúng tôi cũng tạo ra một vài sản phẩm. Một trong số đó là SnapCI, một công cụ tích hợp liên tục (Continuous Integration) được cài đặt và phân phối liên tục (Continuous Delivery – Sau đây cũng viết là Continuous Delivery hoặc CD) (chúng ta sẽ thảo luận về các khái niệm đó sau trong Chương 6). Trước đây, nhóm đã làm việc trên một công cụ tương tự khác, Go-CD, một công cụ Continuous Delivery mã nguồn mở hiện có thể được deploy cục bộ thay vì được cài đạt trên nền tảng điện toán đám mây.
Mặc dù đã có một số mã nguồn được sử dụng lại từ rất sớm giữa các dự án SnapCI và Go-CD, nhưng cuối cùng SnapCI lại trở thành một mã nguồn cơ sở hoàn toàn mới. Tuy nhiên, kinh nghiệm trước đây của nhóm trong lĩnh vực công cụ CD khuyến khích họ tiến nhanh hơn trong việc xác định ranh giới và xây dựng hệ thống của họ như một tập hợp của các microservice.
Tuy nhiên, sau một vài tháng, rõ ràng là các trường hợp sử dụng của SnapCI đã khác nhau một cách tinh tế đến mức việc tiếp nhận các ranh giới dịch vụ ban đầu là không hoàn toàn đúng. Điều này dẫn đến nhiều thay đổi được thực hiện trên các dịch vụ và tất nhiên chi phí cho sự thay đổi cùng gia tăng. Cuối cùng, nhóm đã hợp nhất các dịch vụ trở lại thành một hệ thống monolithic, cho họ thời gian để hiểu rõ hơn về ranh giới nên tồn tại. Một năm sau, nhóm nghiên cứu đã có thể tách hệ thống monolithic thành các microservices, có ranh giới ổn định hơn nhiều. Đây không phải là ví dụ duy nhất về tình huống này mà tôi đã thấy. Việc phân ra sớm một hệ thống thành microservices có thể tốn kém, đặc biệt nếu bạn là người mới tham gia vào lĩnh vực. Theo nhiều cách, việc có một codebase tồn tại mà bạn muốn phân rã thành microservice dễ dàng hơn nhiều so với việc cố gắng xây dựng microservice ngay từ đầu.
Năng lực liên quan đến công việc kinh doanh
Khi bạn bắt đầu nghĩ về các bối cảnh và của chúng giới hạn tồn tại trong tổ chức của mình, bạn không nên nghĩ theo cách dữ liệu được chia sẻ, mà là về khả năng mà các bối cảnh đó cung cấp cho phần còn lại của lĩnh vực. Ví dụ, nhà kho có thể cung cấp khả năng có được danh sách hàng tồn kho hiện tại hoặc bối cảnh tài chính có thể hiển thị các tài khoản cuối tháng hoặc cho phép bạn thiết lập bảng lương cho một đợt tuyển dụng mới. Những khả năng này có thể yêu cầu trao đổi thông tin — các mô hình được chia sẻ — nhưng tôi đã thấy một cách quá thường xuyên việc suy nghĩ về dữ liệu dẫn đến các dịch vụ không có hoạt động gì liên quan đến việc hoạt động của công ty, và chỉ dựa trên các CRUD (tạo, đọc, cập nhật, xóa). Vì vậy, trước tiên hãy đặt hỏi "Bối cảnh này làm gì?", Sau đó là "Vậy nó cần dữ liệu gì để làm điều đó?"
Khi được mô hình hóa dưới dạng dịch vụ, các khả năng này trở thành các hoạt động chính sẽ được hiển thị qua một đường dây với các thành phần khác
Một vấn đề lặp lại vô tận
Nguyên văn: Turtles All the Way Down là một câu thể hiện một vấn đề lặp lại một cách vô tận. https://en.wikipedia.org/wiki/Turtles_all_the_way_down
Khi bắt đầu, bạn có thể sẽ xác định được các bối cảnh một cách chi tiết. Nhưng những bối cảnh này đến lượt nó có thể chứa những bối cảnh khác. Lấy ví dụ, bạn có thể phân chia bối cảnh của nhà kho thành các khả năng liên quan đến hoàn thành đơn đặt hàng, quản lý hàng tồn kho hoặc nhận hàng hóa. Khi xem xét ranh giới của các microservice, trước tiên hãy nghĩ đến các bối cảnh lớn hơn, ít chi tiết hơn, sau đó chia nhỏ theo các bối cảnh lồng nhau này khi bạn đang tìm kiếm lợi ích của việc tách các đường nối giữa chúng ra.
Tôi đã gặp các bối cảnh lồng nhau vẫn bị ẩn trong các bối cảnh khác, hãy kết hợp các microservice với nhau để tạo ra hiệu quả tuyệt vời. Đối với thế giới thực, họ vẫn đang sử dụng các năng lực mà nhà kho cung cấp, nhưng họ không biết rằng các yêu cầu của họ đang thực sự được ánh xạ một cách minh bạch tới hai hoặc nhiều dịch vụ riêng biệt, như bạn có thể thấy trong Hình 3-2. Đôi khi, bạn sẽ quyết định rằng sẽ hợp lý hơn khi bối cảnh giới hạn cấp cao hơn không được mô hình hóa một cách rõ ràng như một ranh giới dịch vụ, như trong Hình 3-3, vì vậy thay vì một ranh giới trong bối cảnh nhà kho duy nhất, thay vào đó bạn có thể chia nhỏ ra thành hàng tồn kho, thực hiện đơn đặt hàng, và nhận hàng hóa.
Hình 3-2. Microservices đại diện cho các bối cảnh được giới hạn lồng nhau ẩn bên trong nhà kho
Hình 3-3. Các bối cảnh giới hạn bên trong nhà kho được bật lên thành bối cảnh cấp cao nhất của riêng chúng
Nói chung, không có quy tắc nào quá nghiêm ngặt về cách tiếp cận nào phù hợp nhất. Tuy nhiên, việc bạn chọn cách tiếp cận lồng vào nhau hay tiếp cận theo cách tách biệt hoàn toàn phải dựa trên cơ cấu tổ chức của bạn. Nếu việc thực hiện đơn hàng, quản lý hàng tồn kho và nhận hàng được quản lý bởi các nhóm khác nhau, thì họ có thể nên được coi là microservice cấp cao nhất. Mặt khác, nếu tất cả chúng được quản lý bởi một nhóm, thì mô hình lồng nhau sẽ có ý nghĩa hơn. Điều này là do sự tác động lẫn nhau của cấu trúc tổ chức và kiến trúc phần mềm, mà chúng ta sẽ thảo luận ở phần cuối của cuốn sách trong Chương 10.
Một lý do khác để cách tiếp cận lồng nhau được ưa thích hơn có thể là chia nhỏ kiến trúc của bạn để đơn giản hóa việc kiểm thử. Ví dụ: khi kiểm thử các dịch vụ sử dụng dịch vụ kho hàng, tôi không phải phân tích từng dịch vụ bên trong bối cảnh của kho hàng, mà chỉ là nhưng API ít chi tiết hơn. Điều này cũng có thể cung cấp cho bạn một đơn vị cô lập khi xem xét các bài kiểm thử trên phạm vi lớn hơn. Ví dụ: tôi có thể quyết định thực hiện các kiểm thử từ đầu đến cuối trong đó tôi khởi chạy tất cả các dịch vụ bên trong bối cảnh kho hàng, nhưng đối với tất cả các service liên quan khác, tôi có thể bỏ qua chúng. Chúng ta sẽ khám phá thêm về kiểm thử và cách ly trong Chương 7.
Vấn đề giao tiếp trong các hoạt động của doanh nghiệp
Những thay đổi mà chúng tôi thực hiện đối với hệ thống của mình thường là những thay đổi mà doanh nghiệp muốn thực hiện về cách hệ thống hoạt động. Chúng tôi đang thay đổi chức năng — khả năng — được hiển thị cho khách hàng của chúng tôi. Nếu hệ thống của chúng tôi được phân tách theo các bối cảnh có giới hạn đại diện cho lĩnh vực của chúng tôi, thì những thay đổi chúng tôi muốn thực hiện có nhiều khả năng bị tách biệt trong một ranh giới microservice duy nhất. Điều này làm giảm số lượng điểm mà chúng tôi cần thực hiện thay đổi và cho phép chúng tôi deploy thay đổi đó một cách nhanh chóng.
Điều quan trọng nữa là phải nghĩ đến sự giao tiếp giữa các dịch vụ nhỏ này theo cùng một cách hiểu về hoạt động trong doanh nghiệp. Việc mô hình hóa phần mềm của bạn và sau đó là toàn bộ công việc kinh doanh không nên dừng lại ở ý tưởng về các bối cảnh có giới hạn. Các định nghĩa và ý tưởng giống nhau được chia sẻ giữa các bộ phận trong tổ chức của bạn phải được phản ánh trong các giao diện. Có thể hữu ích khi nghĩ về các biểu mẫu được gửi giữa các dịch vụ nhỏ này, giống như các biểu mẫu được trong cả tổ chức
Ranh giới về kỹ thuật
Việc xem xét những gì có thể xảy ra khi các dịch vụ được mô hình hóa không chính xác là việc làm hữu ích. Trước đây, tôi cùng một số đồng nghiệp đã làm việc với một khách hàng ở California, giúp công ty áp dụng một số phương pháp lập trình rõ ràng hơn và hướng tới tự động kiểm thử nhiều hơn. Chúng tôi đã bắt đầu với một số công việc đơn giản và có đã kết quả, chẳng hạn như phân tích dịch vụ, khi đó chúng tôi nhận thấy điều gì đó đáng lo ngại hơn nhiều. Tôi không thể đi quá chi tiết về những gì ứng dụng đã làm, nhưng đó là một ứng dụng công khai với lượng lớn khách hàng toàn cầu.
Đội ngũ và hệ thống đã ngày càng lớn mạnh. Ban đầu chỉ là tầm nhìn của một người, hệ thống đã sử dụng ngày càng nhiều tính năng và ngày càng có nhiều người dùng hơn. Cuối cùng, tổ chức quyết định nâng cao năng lực của nhóm bằng cách để một nhóm các developer mới có trụ sở tại Brazil đảm nhận một số công việc. Hệ thống được tách ra, với nửa phía trước của ứng dụng về cơ bản là stateless, deploy một trang web công khai, như trong Hình 3-4. Phần còn lại đơn giản là sử dụng kĩ thuật gọi thủ tục từ xa (RPC) trên một kho dữ liệu. Về cơ bản, hãy tưởng tượng bạn đã sử dụng một lớp kho lưu trữ (repository layer) trong mã nguồn cơ sở của mình và biến đây thành một dịch vụ riêng biệt.
Hình 3-4. Ranh giới dịch vụ được phân chia qua đường nối kỹ thuật
Các thay đổi thường xuyên phải được thực hiện cho cả hai dịch vụ. Cả hai dịch vụ đều nói chuyện với nhau qua các lệnh gọi phương thức kiểu RPC cấp thấp, mà kiểu kết nối này rất cứng nhắc nhưng dễ đứt gãy (chúng ta sẽ thảo luận về vấn đề này trong Chương 4). Giao diện dịch vụ cũng được sử dụng theo cách rất tùy tiện, dẫn đến vấn đề về hiệu suất. Điều này dẫn đến nhu cầu về các cơ chế xử lý RPC theo lô rất phức tạp. Tôi gọi đây là kiến trúc củ hành (onion architecture), vì nó có rất nhiều lớp và khiến tôi phải khóc khi phải cắt qua nó.
Bây giờ về mặt nổi của nó, ý tưởng tách hệ thống monolithic trước đây dưa trên các nhóm địa lý/tổ chức là hoàn toàn hợp lý, vì chúng tôi sẽ mở rộng trong Chương 10. Tuy nhiên, ở đây, thay vì thực hiện theo chiều dọc, tập trung vào các công việc của nghiệp vụ thông qua ngăn xếp, nhóm đã chọn những gì trước đây là đã được tạo ra thành API và tạo ra một lát cắt ngang.
Không phải lúc nào cũng đưa ra quyết định để lập mô hình ranh giới dịch vụ dọc theo các đường nối kỹ thuật cũng sai lầm. Tôi chắc chắn đã thấy điều này rất có ý nghĩa khi một tổ chức đang tìm cách đạt được các mục tiêu hiệu suất nhất định, chẳng hạn. Tuy nhiên, việc tìm kiếm các đường nối này chỉ là động lực phụ của bạn chứ không phải mục tiêu chính.
Tóm tắt
Trong chương này, bạn đã tìm hiểu một chút về điều gì tạo nên một dịch vụ tốt và cách tìm các đường nối trong không gian vấn đề của chúng tôi, mang lại cho chúng tôi lợi ích kép của cả khớp nối lỏng và tính liên kết cao. Các bối cảnh có giới hạn là một công cụ quan trọng giúp chúng ta tìm ra các đường nối này và bằng cách sắp xếp các microservice của chúng ta theo các ranh giới này, chúng ta cần đảm bảo rằng hệ thống cuối cùng có mọi cơ hội để giữ nguyên các tính năng đó. Chúng ta cũng đã có một gợi ý về cách chúng ta có thể chia nhỏ các microservice của mình hơn nữa, điều gì đó chúng ta sẽ khám phá sâu hơn sau. Và chúng ta cũng đã giới thiệu MusicCorp, lĩnh vực ví dụ mà chúng ta sẽ sử dụng trong suốt cuốn sách này.
Những ý tưởng được trình bày trong Thiết kế theo hướng lĩnh vực – DDD của Eric Evans rất hữu ích cho chúng ta trong việc tìm ra ranh giới hợp lý cho các dịch vụ và tôi mới chỉ sơ lược ở đây. Tôi giới thiệu cuốn sách Implementing Domain-Driven Design (Addison- Wesley) của Vaughn Vernon để giúp bạn hiểu tính thực tiễn của phương pháp này.
Mặc dù chương này chủ yếu là những thứ mang tính tổng quát, nhưng chúng ta cần đi sâu vào các kỹ thuật hơn trong các chương tiếp theo. Có rất nhiều cạm bẫy liên quan đến việc deploy giao diện giữa các dịch vụ, mà chúng có thể dẫn đến tất cả các loại rắc rối và chúng ta sẽ phải đi sâu vào chủ đề này nếu muốn giữ cho hệ thống của mình không trở thành một gã khổng lồ, rối rắm lộn xộn.
Như chúng ta đã thấy cho đến nay, microservice cung cấp cho chúng ta rất nhiều sự lựa chọn và theo đó là rất nhiều quyết định để đưa ra. Ví dụ, chúng ta nên sử dụng bao nhiêu công nghệ khác nhau, chúng ta nên để các nhóm khác nhau sử dụng các thành ngữ lập trình khác nhau, và chúng ta có nên tách hoặc hợp nhất một dịch vụ không? Làm thế nào để chúng ta đưa ra những quyết định này? Với tốc độ thay đổi nhanh hơn và môi trường linh hoạt hơn mà các kiến trúc này cho phép, vai trò của kiến trúc sư của hệ thống cũng phải thay đổi. Trong chương này, tôi sẽ có một cái nhìn khá chắc chắn về vai trò của một kiến trúc sư của hệ thống là gì và hy vọng sẽ khởi động một cuộc tấn công cuối cùng vào toà tháp ngà voi (ám chỉ việc xa rời thực tế)
Việc so sánh khập khiễng
Bạn tiếp tục sử dụng từ đó. Nhưng tôi không nghĩ rằng nó có nghĩa là những gì bạn nghĩ nó.
— Inigo Montoya, từ Cô dâu công chúa
Kiến trúc sư của hệ thống là một công việc quan trọng. Họ chịu trách nhiệm đảm bảo rằng chúng ta có một tầm nhìn kỹ thuật hợp nhất, một tầm nhìn sẽ giúp hệ thống của chúng tôi có thể đáp ứng được nhu cầu của khách hàng. Ở một số công ty, họ có thể chỉ phải làm việc với một nhóm, trong trường hợp đó, vai trò của họ và trưởng nhóm kỹ thuật thường giống nhau. Ở những người khác, họ có thể xác định tầm nhìn cho toàn bộ chương trình làm việc, phối hợp với nhiều nhóm trên toàn thế giới, hoặc thậm chí có thể là toàn bộ tổ chức. Ở bất kỳ cấp độ nào họ hoạt động, vai trò này rất khó để xác định rõ ràng, và mặc dù nó thường là một bước tiến nghề nghiệp rõ ràng cho developer trong các tổ chức doanh nghiệp, nó cũng là một vai trò nhận được nhiều chỉ trích hơn hầu hết vai trò bất kỳ nào khác. Hơn bất kỳ vai trò nào khác, họ có thể có tác động trực tiếp đến chất lượng của các hệ thống được xây dựng, đến điều kiện làm việc của đồng nghiệp và khả năng của tổ chức của họ để đáp ứng với sự thay đổi, nhưng dường như chúng ta thường làm sai vài trò của công việc này. Tại sao vậy?
Ngành công nghiệp (phần mềm) của chúng ta là một ngành trẻ. Đây là điều mà chúng ta dường như đã quên, nhưng chúng ta chỉ tạo ra các chương trình chạy trên thứ mà chúng ta gọi là máy tính trong khoảng 70 năm trở lại đây. Vì vậy, chúng ta không ngừng tìm kiếm một thứ tương tự ở các ngành nghề khác để cố gắng giải thích những gì chúng ta đang làm. Chúng ta không phải là bác sĩ hay kỹ sư y tế, nhưng cũng không phải là thợ sửa ống nước hay thợ điện. Thay vào đó, chúng ta thường nằm ở lưng chừng, điều này khiến xã hội khó hiểu chúng ta, hoặc chúng ta không hiểu chúng ta đang ở đâu.
Vì vậy, chúng ta vay mượn từ các ngành nghề khác. Chúng tôi tự gọi mình là “kỹ sư” phần mềm, hoặc"Kiến trúc sư." Nhưng chúng ta không phải kiến trúc sư, nhỉ? Kiến trúc sư và kỹ sư (ở ngành khác) có một sự nghiêm khắc và tinh tế mà chúng ta chỉ có thể mơ ước, và tầm quan trọng của họ trong xã hội được hiểu rõ. Tôi nhớ đã nói chuyện với một người bạn của tôi, một ngày trước khi anh ta trở thành một kiến trúc sư có trình độ. “Ngày mai,” anh ấy nói, "nếu tôi cho bạn lời khuyên ở quán rượu về cách xây dựng một thứ gì đó và điều đó là sai, tôi sẽ phải chịu trách nhiệm. Tôi có thể bị kiện, vì theo pháp luật, tôi bây giờ là một kiến trúc sư có năng lực và tôi phải chịu trách nhiệm nếu tôi làm sai. " Tầm quan trọng của những công việc này đối với xã hội có nghĩa là cần phải có những bằng cấp cần thiết để đáp ứng. Ví dụ, ở Anh, bạn phải học tối thiểu bảy năm trước khi được gọi là kiến trúc sư. Nhưng những công việc này cũng dựa trên một lượng kiến thức có từ hàng nghìn năm trước. Và chúng ta? Không hẳn. Đó cũng là lý do tại sao tôi xem hầu hết các hình thức chứng chỉ CNTT là vô giá trị, vì chúng hầu như không thể khẳng định chúng ta tốt như thế nào.
Một phần trong chúng ta có nhu cầu được công nhận, vì vậy chúng tôi mượn tên từ những ngành nghề khác đã có được sự công nhận mà chúng tôi là một ngành khao khát. Nhưng điều này có thể gây hại gấp đôi. Đầu tiên, nó ngụ ý rằng chúng ta biết mình đang làm gì, khi nào thì rõ ràng là không. Tôi sẽ không nói rằng các công trình xây dựng và cầu nối không bao giờ sụp đổ, nhưng tỉ lệ ít hơn nhiều so với số lần các chương trình của chúng ta sẽ gặp sự cố, khiến cho việc so sánh với các kỹ sư là khập khiễng. Thứ hai, các phép loại suy bị chia nhỏ rất nhanh khi chỉ nhìn lướt qua. Để xoay chuyển tình thế, nếu việc xây dựng cây cầu giống như một chương trình, chúng tôi sẽ phát hiện ra rằng bờ biển xa bây giờ đã xa hơn 50 mét, rằng nó thực sự là bùn chứ không phải đá granit, và thay vì xây dựng một cây cầu mà đáng lẽ chúng tôi sẽ xây dựng – thay vào đó là một cây cầu đường bộ. Phần mềm của chúng ta không bị ràng buộc bởi các quy tắc vật lý giống như các kiến trúc sư hoặc kỹ sư thực sự phải đáp ứng và những gì chúng tôi tạo ra được thiết kế để linh hoạt, thích ứng và phát triển theo yêu cầu của người dùng.
Có lẽ thuật ngữ kiến trúc sư(Solution Architect hay Software Architect) đã gây hại nhiều nhất. Ý tưởng về một người lập kế hoạch chi tiết cho người khác giải thích và mong muốn điều này được thực hiện. Một sự cân bằng giữa một nghệ sỹ và một kỹ sư, giám sát việc tạo của những thứ tông thường với một tầm nhìn duy nhất, với tất cả các quan điểm khác là không phù hợp, ngoại trừ sự phản đối không thường xuyên từ kỹ sưvề các quy luật vật lý. Trong ngành của chúng ta, quan điểm này của kiến trúc sư của hệ thống dẫn đến một số thực hành khủng khiếp. Sơ đồ này đến sơ đồ khác, trang này qua trang khác của tài liệu, được tạo ra với mục đích cung cấp thông tin về việc xây dựng một hệ thống hoàn hảo, mà không tính đến tương lai về cơ bản không thể biết trước được. Hoàn toàn không có bất kỳ hiểu biết nào về mức độ khó thực hiện, hoặc chỉ là nó có thực sự hoạt động hay không, hãy để một mình có khả năng thay đổi khi chúng ta có hiểu biết nhiều hơn.
Khi chúng ta so sánh mình với các kỹ sư hoặc kiến trúc sư, chúng ta có nguy cơ khiến mọi người trở thành kẻ phá hoại. Thật không may, chúng tôi đang mắc kẹt với từ kiến trúc sư cho đến bây giờ. Vì vậy, điều tốt nhất chúng ta có thể làm là xác định lại ý nghĩa của nó trong ngữ cảnh của chúng ta.
Một tầm nhìn tiến hóa cho kiến trúc sư
Các yêu cầu của chúng tôi thay đổi nhanh hơn so với những người thiết kế và xây dựng các tòa nhà — các công cụ và kỹ thuật theo ý của chúng tôi cũng vậy. Những thứ chúng ta tạo ra không phải là những điểm cố định trong thời gian. Sau khi được đưa vào môi trường production, phần mềm của chúng tôi sẽ tiếp tục phát triển khi cách thức sử dụng thay đổi. Đối với hầu hết những thứ chúng tôi tạo ra, chúng tôi phải chấp nhận rằng một khi phần mềm đến tay khách hàng, chúng tôi sẽ phải phản ứng và thích nghi, thay vì nó là một sản phẩm không bao giờ thay đổi. Do đó, các kiến trúc sư của chúng tôi cần phải thay đổi suy nghĩ của họ thay vì việc tạo ra sản phẩm cuối cùng hoàn hảo, mà tập trung vào việc giúp tạo ra một framework trong đó các hệ thống phù hợp có thể xuất hiện và tiếp tục phát triển khi chúng tôi có hiểu biết nhiều hơn.
Mặc dù cho đến nay, tôi đã dành phần lớn thời lượng của chương để cảnh báo bạn không nên so sánh bản thân của chúng ta quá nhiều với các ngành nghề khác, nhưng có một điểm tương đồng mà tôi thích khi nói đến vai trò của kiến trúc sư của hệ thống CNTT và tôi nghĩ tốt hơn nên gói gọn những gì chúng ta muốn. vai trò hiện hữu. Erik Doernenburg lần đầu tiên chia sẻ với tôi ý tưởng rằng chúng ta nên nghĩ về vai trò của mình với tư cách là nhà quy hoạch thành phố hơn là kiến trúc sư cho môi trường xây dựng. Vai trò của người lập kế hoạch thị trấn hẳn đã quen thuộc với bất kỳ bạn nào đã chơi SimCity trước đây. Vai trò của người lập quy hoạch thành phố là xem xét vô số nguồn thông tin và sau đó cố gắng tối ưu hóa bố cục của thành phố để phù hợp nhất với nhu cầu của người dân hiện nay, có tính đến việc sử dụng trong tương lai. Tuy nhiên, cách anh ấy ảnh hưởng đến cách thành phố phát triển là rất thú vị. Anh ta không nói, “xây dựng tòa nhà cụ thể này ở đó”; thay vào đó, anh ta tạo ra các vùng trong**thành phố. Vì vậy, như ở SimCity, bạn có thể chỉ định một phần thành phố của mình là khu công nghiệp và một phần khác là khu dân cư. Sau đó, những người khác sẽ quyết định những tòa nhà chính xác được tạo ra, nhưng có những hạn chế: nếu bạn muốn xây dựng một nhà máy, nó sẽ cần phải ở trong một khu công nghiệp. Thay vì lo lắng quá nhiều về những gì xảy ra trong một khu vực, thay vào đó, người lập kế hoạch thành phố sẽ dành nhiều thời gian hơn để tìm hiểu cách thức di chuyển của mọi người và các tiện ích từ khu vực này sang khu vực khác.
Nhiều người đã ví một thành phố như một sinh vật sống. Thành phố thay đổi theo thời gian. Nó thay đổi và phát triển khi những cư dân sử dụng nó theo những cách khác nhau, hoặc khi các lực lượng bên ngoài định hình nó. Người quy hoạch thành phố cố gắng hết sức để dự đoán những thay đổi này, nhưng chấp nhận rằng việc cố gắng kiểm soát trực tiếp tất cả các khía cạnh của những gì xảy ra là vô nghĩa.
Việc so sánh với phần mềm nên rõ ràng. Khi người dùng sử dụng phần mềm của chúng tôi, chúng tôi cần phản ứng và thay đổi. Chúng ta không thể lường trước mọi điều sẽ xảy ra, và vì vậy thay vì lập kế hoạch cho bất kỳ trường hợp nào, chúng ta nên lên kế hoạch cho phép thay đổi bằng cách tránh việc tạo ra một điều cuối cùng không bao giờ thay đổi. Thành phố của chúng ta — hệ thống — cần phải là một nơi tốt đẹp, hạnh phúc cho tất cả những ai sử dụng nó. Một điều mà mọi người thường quên là hệ thống của chúng tôi không chỉ đáp ứng người dùng; nó cũng tạo điều kiện cho các developer và nhà kinh doanh, những người cũng phải làm việc ở đó và những người có công việc đảm bảo rằng nó có thể thay đổi theo yêu cầu. Để mượn một thuật ngữ từ Frank Buschmann, các kiến trúc sư của hệ thống có nhiệm vụ đảm bảo rằng hệ thống cũng có thể sử dụng được cho các developer.
Một nhà quy hoạch, cũng giống như một kiến trúc sư, cũng cần biết khi nào kế hoạch của mình không được sử dụng hoặc tuân thủ. Vì anh ta có ít chỉ thị hơn, nên số lần anh ta cần tham gia để chỉ ra sự đúng sai là ít nhất, nhưng nếu ai đó quyết định xây dựng một nhà máy xử lý nước thải trong khu dân cư, anh ta cần phải có khả năng đóng cửa nó.
Vì vậy, các kiến trúc sư của hệ thống của chúng tôi với tư cách là những nhà quy hoạch thị trấn cần phải định hướng theo hướng tổng quan và chỉ tham gia vào việc cụ thể hóa chi tiết thực hiện trong một số trường hợp cụ thể. Họ cần đảm bảo rằng hệ thống phù hợp với mục đích ngay bây giờ, nhưng cũng là một nền tảng cho tương lai. Và họ cần đảm bảo rằng đó là một hệ thống khiến người dùng và developer hài lòng như nhau. Điều này nghe có vẻ như một yêu cầu khá cao. Vậy, chúng ta bắt đầu từ đâu?
Phân vùng
Vì vậy, tiếp tục ẩn dụ về kiến trúc sư của hệ thống là người quy hoạch thành phố thêm một chút nữa, chúng ta có những khu vực nào? Đây là các ranh giới dịch vụ của chúng tôi hoặc có thể là danh sách các dịch vụ được nhóm lại một cách chi tiết. Là kiến trúc sư, chúng ta cần ít để ý về những gì xảy ra bên trong từng khu vực hơn là những gì xảy ra giữa các khu vực với nhau. Điều đó có nghĩa là chúng tôi cần dành thời gian suy nghĩ về cách các dịch vụ của chúng tôi nói chuyện với nhau hoặc đảm bảo rằng chúng tôi có thể duy trì tình trạng hoạt động tổng thể của hệ thống một cách chính xác. Mức độ tham gia của chúng tôi vào bên trong khu vực sẽ khác nhau
một phần nào đó. Nhiều tổ chức đã áp dụng microservices để tối đa hóa quyền tự chủ của các nhóm, điều mà chúng tôi sẽ mở rộng trong Chương 10. Nếu bạn ở trong một tổ chức như vậy, bạn sẽ dựa nhiều hơn vào nhóm đó để đưa ra quyết định phù hợp tại từng chỗ.
Nhưng giữa các khu vực, hoặc các ô trên bản đồ kiến trúc truyền thống của chúng tôi, chúng tôi cần phải cẩn thận; làm sai ở đây dẫn đến tất cả các loại vấn đề và có thể rất khó sửa chữa.
Trong mỗi dịch vụ, bạn có thể đồng ý với nhóm sở hữu khu vực đó chọn một kho lưu trữ dữ liệu hoặc công nghệ khác nhau. Tất nhiên, những mối quan tâm khác có thể xuất hiện ở đây. Xu hướng để các nhóm chọn công cụ phù hợp cho công việc của bạn có thể bị hạn chế bởi thực tế là việc thuê người hoặc di chuyển họ giữa các nhóm trở nên khó khăn hơn nếu bạn có 10 ngăn xếp công nghệ khác nhau để hỗ trợ. Tương tự, nếu mỗi đội chọn một kho dữ liệu hoàn toàn khác nhau, bạn có thể thấy mình thiếu đủ kinh nghiệm để chạy bất kỳ kho dữ liệu nào trong số họ trên quy mô lớn. Netflix, ví dụ, hầu hết đã chuẩn hóa và sử dụng Cassandra như một kho lưu trữ dữ liệu. Mặc dù nó có thể không hẳn đã là công nghệ phù hợp nhất cho tất cả các trường hợp, nhưng Netflix cảm thấy rằng giá trị thu được bằng cách xây dựng công cụ và kiến thức chuyên môn xung quanh Cassandra quan trọng hơn việc phải hỗ trợ và vận hành trên quy mô nhiều nền tảng khác có thể phù hợp hơn cho từng các nhiệm vụ nhất định. Netflix là một ví dụ điển hình, trong đó quy mô có thể là yếu tố mang tính quyết định mạnh nhất, bạn có thể thấy ý tưởng đó khá rõ ràng.
Tuy nhiên, phần giữa các dịch vụ với nhau mới là nơi mọi thứ có thể trở nên lộn xộn. Nếu một dịch vụ quyết định cung cấp REST thông qua HTTP, một dịch vụ khác sử dụng giao thức bộ đệm và một dịch vụ thứ ba sử dụng Java RMI, thì việc tích hợp chúng có thể trở thành một cơn ác mộng vì các dịch vụ sử dụng chúng phải chịu và hỗ trợ nhiều kiểu giao thức. Đây là lý do tại sao tôi cố gắng bám sát tôn chỉ rằng chúng ta nên “lo lắng về những gì xảy ra giữa các hộp và tự do trong những gì xảy ra bên trong.”
Kiến trúc mã nguồn
Nếu chúng tôi muốn đảm bảo rằng các hệ thống chúng tôi tạo ra có thể sử dụng được cho các developer của chúng tôi, thì các kiến trúc sư của chúng tôi cần phải hiểu tác động của các quyết định của họ. Ít nhất, điều này có nghĩa là dành thời gian cho nhóm và lý tưởng là nó có nghĩa là những developer này cũng thực sự dành thời gian viết mã nguồn cho nhóm. Đối với những bạn thực hành phát triển theo cặp**(pair-programing),** việc một kiến trúc sư tham gia nhóm trong một thời gian ngắn với tư cách là một thành viên của cặp sẽ trở thành một vấn đề đơn giản. Tốt nhất, bạn nên làm những câu chuyện bình thường, để thực sự hiểu công việc bình thường là như thế nào. Tôi không thể nhấn mạnh tầm quan trọng của kiến trúc sư khi làm việc cùng nhóm! Điều này hiệu quả hơn đáng kể so với việc gọi điện hoặc chỉ nhìn vào mã nguồn của cô ấy.
Về mức độ thường xuyên mà bạn nên làm điều này, điều đó phụ thuộc rất nhiều vào quy mô của (các) nhóm mà bạn đang làm việc. Nhưng điều quan trọng là nó phải là một hoạt động thường xuyên. Ví dụ: nếu bạn đang làm việc với bốn nhóm, dành nửa ngày cho mỗi nhóm bốn tuần một lần đảm bảo bạn xây dựng nhận thức và cải thiện giao tiếp với các nhóm mà bạn đang làm việc.
Phương pháp tiếp cận có nguyên tắc
Các quy tắc dành cho sự vâng lời của những kẻ ngu ngốc và sự hướng dẫn của những nhà thông thái.
— Thường được cho là của Douglas Bader
Việc đưa ra quyết định trong thiết kế hệ thống, tất cả chỉ xoay quanh sự đánh đổi và microservice mang đến cho chúng ta rất nhiều đánh đổi! Khi chọn một kho dữ liệu, chúng ta có chọn một nền tảng mà chúng ta có ít kinh nghiệm, nhưng điều đó mang lại cho chúng ta khả năng mở rộng tốt hơn không? Chúng tôi có thể sử dụng hai stack công nghệ khác nhau trong hệ thống của mình không? Vậy nếu sử dụng ba thì sao? Một số quyết định có thể được thực hiện hoàn toàn ngay tại chỗ với thông tin có sẵn cho chúng tôi, và đây là những thứ dễ thực hiện nhất. Nhưng những quyết định có thể phải được thực hiện trên thông tin không đầy đủ thì sao?
Việc lập khung ở đây có thể hữu ích và một cách tuyệt vời để giúp lập khung cho việc ra quyết định của chúng ta là xác định một bộ các nguyên tắc và thực hành hướng dẫn nó, dựa trên các mục tiêu mà chúng ta đang cố gắng đạt được. Chúng ta hãy xem xét từng thứ một.
Mục tiêu chiến lược
Vai trò của kiến trúc sư đã đủ khó khăn, vì vậy, may mắn là chúng tôi thường không phải xác định các mục tiêu chiến lược! Các mục tiêu chiến lược phải nói lên được vị trí của công ty và cách công ty tự thấy là tốt nhất để làm cho khách hàng hài lòng. Đây sẽ là những mục tiêu cấp cao và có thể hoàn toàn không bao gồm công nghệ. Chúng có thể được xác định ở cấp độ công ty hoặc cấp độ phòng ban. Chúng có thể là những thứ như “Mở rộng sang Đông Nam Á để mở khóa các thị trường mới” hoặc “Hãy để khách hàng đạt được càng nhiều càng tốt bằng cách sử dụng các dịch vụ tự phục vụ.” Điều quan trọng là đây là nơi tổ chức của bạn đứng đầu, vì vậy bạn cần đảm bảo công nghệ phù hợp với nó.
Nếu bạn là người xác định tầm nhìn kỹ thuật của công ty, điều này có thể có nghĩa là bạn sẽ cần dành nhiều thời gian hơn cho các bộ phận phi kỹ thuật trong tổ chức của mình (hoặc bộ phận kinh doanh, như chúng thường được gọi). Tầm nhìn thúc đẩy cho doanh nghiệp là gì? Và nó thay đổi như thế nào?
Nguyên tắc
Nguyên tắc là những quy tắc bạn đã thực hiện để điều chỉnh những gì bạn đang làm với một số mục tiêu lớn hơn và đôi khi sẽ thay đổi. Ví dụ: nếu một trong những mục tiêu chiến lược của bạn với tư cách là một tổ chức là giảm thời gian triển khai các tính năng mới, bạn có thể xác định một nguyên tắc nói rằng nhóm delivery có toàn quyền kiểm soát vòng đời của phần mềm của họ để deliver bất cứ khi nào họ sẵn sàng, độc lập với bất kỳ đội nào khác. Nếu một mục tiêu khác là tổ chức của bạn là dịch chuyển mạnh mẽ sang việc triển khai dịch vụ của mình ở các quốc gia khác, bạn có thể quyết định thực hiện một nguyên tắc rằng toàn bộ hệ thống phải có tính di động để cho phép nó được deploy tại các quốc gia đó nhằm tôn trọng chủ quyền của dữ liệu.
Bạn hoàn toàn không muốn có vô số nguyên tắc. Ít hơn 10 là một con số tốt – đủ nhỏ để mọi người có thể nhớ chúng hoặc để phù hợp với các áp phích nhỏ. Bạn càng có nhiều nguyên tắc, thì khả năng chúng trùng lặp hoặc mâu thuẫn với nhau càng lớn.
12 Factors của Heroku là một tập hợp các nguyên tắc thiết kế có cấu trúc xoay quanh mục tiêu giúp bạn tạo ra các ứng dụng hoạt động tốt trên nền tảng Heroku. Chúng cũng có thể có ý nghĩa trong các ngữ cảnh khác. Một số nguyên tắc thực sự là những ràng buộc dựa trên các hành vi mà ứng dụng của bạn cần thể hiện để hoạt động trên Heroku. Ràng buộc thực sự là một thứ rất khó (hoặc hầu như không thể) thay đổi, trong khi các nguyên tắc là thứ chúng ta quyết định lựa chọn. Bạn có thể quyết định gọi rõ ràng những điều đó là nguyên tắc so với những điều là ràng buộc, để giúp chỉ ra những điều bạn thực sự không thể thay đổi. Cá nhân tôi nghĩ rằng có thể có một số giá trị trong việc giữ chúng trong cùng một danh sách để khuyến khích những ràng buộc đầy thử thách thỉnh thoảng và xem liệu chúng có thực sự bất di bất dịch hay không!
Thực hành
Thực hành của chúng tôi là cách chúng tôi đảm bảo các nguyên tắc của chúng tôi đang được thực hiện. Chúng là một tập hợp các hướng dẫn chi tiết, thiết thực để thực hiện các nhiệm vụ. Chúng thường sẽ là công nghệ cụ thể, và phải đủ đơn giản để bất kỳ developer nào cũng có thể hiểu được chúng. Các phương pháp thực hành có thể bao gồm hướng dẫn mã hóa, thực tế là tất cả dữ liệu log cần được quản lý tập trung hoặc HTTP / REST là kiểu tích hợp tiêu chuẩn. Do bản chất kỹ thuật của chúng, các thực hành thường sẽ thay đổi thường xuyên hơn các nguyên tắc.
Cũng như các nguyên tắc, đôi khi thực hành phản ánh những hạn chế trong tổ chức của bạn. Ví dụ: nếu bạn chỉ hỗ trợ CentOS, điều này sẽ cần được phản ánh trong thực hành của bạn.
Thực hành nên làm nền tảng cho các nguyên tắc của chúng tôi. Một nguyên tắc nêu rõ rằng các nhóm deliver vận hành toàn bộ vòng đời của hệ thống của họ có thể có nghĩa là bạn có một thông lệ cho rằng tất cả các dịch vụ được deploy vào các tài khoản AWS riêng biệt, cung cấp khả năng tự quản lý tài nguyên và cách ly khỏi các nhóm khác.
Kết hợp các nguyên tắc và thực hành
Nguyên tắc của một người là thực hành của người khác. Ví dụ, bạn có thể quyết định gọi việc sử dụng HTTP/REST là một nguyên tắc hơn là một thực hành. Và điều đó sẽ ổn thôi. Điểm mấu chốt là giá trị khi có những ý tưởng bao quát hướng dẫn cách hệ thống phát triển và đủ chi tiết để mọi người biết cách thực hiện những ý tưởng đó. Đối với một nhóm đủ nhỏ, có lẽ là một nhóm duy nhất, việc kết hợp các nguyên tắc và thực hành có thể ổn. Tuy nhiên, đối với các tổ chức lớn hơn, nơi công nghệ và phương thức làm việc có thể khác nhau, bạn có thể muốn có một bộ thực hành khác ở những nơi chỗ khác nhau, miễn là cả hai đều hướng tới một bộ nguyên tắc chung. Ví dụ, một nhóm .NET có thể có một bộthực hành và một nhóm Java khác, với một bộ thực hành chung cho cả hai. Tuy nhiên, các nguyên tắc có thể giống nhau cho cả hai.
Một ví dụ trong thế giới thực
Đồng nghiệp của tôi, Evan Bottcher, đã phát triển sơ đồ thể hiện trong Hình 2-1 trong quá trình làm việc với một trong những khách hàng của chúng tôi. Hình này cho thấy sự tác động lẫn nhau của các mục tiêu, nguyên tắc và thực hành theo một định dạng rất rõ ràng. Trong vòng một vài năm, các hoạt động ở ngoài cùng bên phải sẽ thay đổi khá thường xuyên, trong khi các nguyên tắc vẫn khá tĩnh. Một sơ đồ như thế này có thể được in một cách độc đáo trên một tờ giấy và được chia sẻ, và mỗi ý tưởng đủ đơn giản để các developer trung bình có thể ghi nhớ. Tất nhiên, có nhiều chi tiết hơn đằng sau mỗi điểm ở đây, nhưng có thể trình bày rõ điều này ở dạng tổng hợp là rất hữu ích.
Hình 2-1. Một ví dụ thực tế về các nguyên tắc và thực hành
Có lý do để có tài liệu hỗ trợ một số mục này. Tuy nhiên, về cơ bản, tôi thích ý tưởng có một bộ mã nguồn mẫu mà bạn có thể xem, kiểm tra và chạy, là hiện thân của những ý tưởng này. Thậm chí tốt hơn, chúng ta có thể tạo ra công cụ làm đúng việc. Chúng ta sẽ thảo luận sâu hơn về vấn đề đó trong giây lát.
Tiêu chuẩn bắt buộc
Khi bạn đang nghiên cứu các phương pháp của mình và suy nghĩ về những đánh đổi mà bạn cần thực hiện, một trong những điểm cân bằng cốt lõi cần tìm là mức độ biến thiên cho phép trong hệ thống của bạn. Một trong những cách quan trọng để xác định những gì nên không đổi từ dịch vụ này sang dịch vụ khác là xác định một dịch vụ tốt, hoạt động tốt trông như thế nào. Dịch vụ “tốt” trong hệ thống của bạn là gì? Nó cần có những khả năng nào để đảm bảo rằng hệ thống của bạn có thể quản lý được và một dịch vụ không tốt sẽ không làm hỏng toàn bộ hệ thống? Và, cũng như với mọi người, những gì một công dân tốt trong một bối cảnh không phản ánh những gì nó trông như thế nào ở một nơi khác. Tuy nhiên, có một số đặc điểm chung của các dịch vụ hoạt động tốt mà tôi nghĩ là khá quan trọng để quan sát. Đây là một số lĩnh vực chính mà việc mọi thứ có quá nhiều khác biệt có thể dẫn đến một thời kì cực kì khó khăn. Như Ben Christensen từ Netflix đã nói, khi chúng ta nghĩ về bức tranh lớn hơn, “nó cần phải là một hệ thống gắn kết được tạo thành từ nhiều bộ phận nhỏ có vòng đời tự trị nhưng tất cả lại kết hợp với nhau”. Vì vậy, chúng tôi cần tìm sự cân bằng giữa việc tối ưu hóa choquyền tự chủ của từng microservice mà không làm hỏngbức tranh toàn cảnh. Xác định các thuộc tính rõ ràng mà mỗi dịch vụ phải có là một cách để xác định rõ ràng sự cân bằng đó nằm ở đâu.
Giám sát
Điều cốt lõiở đây là chúng tôi có thể vẽ ra các quan điểm nhất quán, tầm nhìn xuyên suốt các service về tình trạng sức khoẻ của hệ thống. Đây phải là cái nhìn toàn hệ thống, không phải là từng dịch vụ đơn lẻ. Như chúng ta sẽ thảo luận trong Chương 8, việc biết tình trạng của từng dịch vụ là hữu ích, nhưng thường chỉ khi bạn đang cố gắng chẩn đoán một vấn đề rộng hơn hoặc hiểu một xu hướng lớn hơn. Để làm cho điều này dễ dàng nhất có thể, tôi khuyên bạn nên đảm bảo rằng tất cả các dịch vụ cần đưa ra các chỉ số liên quan đến sức khỏe và giám sát chung theo cùng một cách.
Bạn có thể chọn áp dụng cơ chế đẩy (push), trong đó mỗi dịch vụ cần đẩy dữ liệu này vào một trung tâm xử lý. Đối với các chỉ số, đây có thể là Graphite, và đối với sức khoẻ của hệ thống, nó có thể là Nagios. Hoặc bạn có thể quyết định sử dụng hệ thống polling để lấy dữ liệu từ chính các nút. Nhưng bất cứ điều gì bạn chọn, hãy cố gắng giữ cho nó được chuẩn hóa. Làm cho công nghệ bên trong hộp trở nên mờ đục và không yêu cầu hệ thống giám sát của bạn phải thay đổi để hỗ trợ nó. Có một yêu cầu ở đây: log cần đặt tập trung ở một chỗ
Giao diện – Giao thức
Chọn một số lượng nhỏ các công nghệ giao diện đã xác định sẽ giúp tích hợp những người dùng mới. Một là một con số tốt khi nói về số lượng tiêu chuẩn. Hai cũng không quá tệ. Có 20 kiểu tích hợp khác nhau thì không tốt. Đây không chỉ là việc chọn công nghệ và giao thức. Ví dụ: nếu bạn chọn HTTP/REST, bạn sẽ sử dụng động từ hay danh từ? Bạn sẽ xử lý việc phân trang tài nguyên như thế nào? Bạn sẽ xử lý việc lập phiên bản của các điểm cuối như thế nào?
An toàn về mặt kiến trúc
Chúng tôi không thể để một dịch vụ có hành vi xấu làm hỏng bữa tiệc của tất cả mọi người. Chúng tôi phải đảm bảo rằng các dịch vụ của chúng tôi bảo vệ bản thân chúng khỏi nhưng vấn đề như downtime hay không thể gọi đến dịch vụ. Chúng ta càng có nhiều dịch vụ mà không xử lý đúng về khả năng thất bại của các dịch vụ khác khi gọi đến, thì hệ thống của chúng ta sẽ càng trở nên mong manh hơn. Điều này có nghĩa là bạn có thể sẽ muốn bắt buộc tối thiểu mỗi dịch vụ mà gọi đến dịch vụ khác phải có connection pool riêng và bạn thậm chí có thể đi xa hơn khi nói rằng mỗi dịch vụ cũng sử dụng một bộ ngắt mạch (circuit breaker). Điều này sẽ được đề cập sâu hơn khi chúng ta thảo luận về microservices ở quy mô lớn trong Chương 11.
Chơi theo luật cũng quan trọng khi nói đến mã phản hồi (response code). Nếu bộ ngắt mạch của bạn dựa vào mã HTTP và một dịch vụ quyết định gửi lại mã 2XX do lỗi hoặc nhầm lẫn mã 4XX với mã 5XX, thì các biện pháp an toàn này có thể bị ảnh hưởng. Các mối quan tâm tương tự sẽ áp dụng ngay cả khi bạn không sử dụng HTTP; hiểu sự khác biệt giữa một yêu cầu OK và được xử lý chính xác, một yêu cầu không tốt và ngăn dịch vụ làm bất cứ điều gì với nó và một yêu cầu có thể OK nhưng chúng tôi không thể biết được vì máy chủ không hoạt động là chìa khóa để đảm bảo chúng tôi có thể thất bại nhanh chóng và theo dõi các vấn đề. Nếu các dịch vụ của chúng tôi hoạt động nhanh và lỏng lẻo với các quy tắc này, chúng tôi sẽ dẫn đến một hệ thống dễ bị tấn công hơn.
Quản trị thông qua quy tắc
Cùng nhau và thống nhất về cách mọi thứ có thể được thực hiện là một ý kiến hay. Nhưng dành thời gian để đảm bảo rằng mọi người đang tuân theo các nguyên tắc này sẽ kém thú vị hơn, vì đang đặt gánh nặng lên các developer trong việc deploy tất cả những điều tiêu chuẩn này mà bạn mong đợi mỗi dịch vụ thực hiện. Tôi rất tin tưởng vào việc giúp bạn dễ dàng làm điều đúng đắn. Hai kỹ thuật mà tôi thấy hoạt động tốt ở đây là sử dụng các mẫu và cung cấp các khuôn mẫu dịch vụ.
Người làm mẫu
Viết tài liệu là tốt và hữu ích. Tôi thấy rõ giá trị, nên sau tất cả tôi đã viết cuốn sách này. Nhưng các developer cũng thích viết mã nguồn, và mã nguồn là thứ mà họ có thể chạy và khám phá. Nếu bạn có một bộ tiêu chuẩn hoặc phương pháp hay nhất mà bạn muốn khuyến khích, thì việc có những ví dụ mẫu mà bạn có thể chỉ cho mọi người sẽ hữu ích. Ý tưởng là mọi người không thể sai lầm chỉ bằng cách bắt chước một số bộ phận tốt hơn trong hệ thống của bạn.
Lý tưởng nhất, đây phải là những dịch vụ trong thế giới thực mà bạn có để làm mọi thứ ổn thỏa, chứ không phải là những dịch vụ biệt lập chỉ được deploy để trở thành những ví dụ hoàn hảo. Bằng cách đảm bảo rằng những ví dụ mẫu của bạn thực sự đang được sử dụng, bạn đảm bảo rằng tất cả các nguyên tắc bạn thực hiện thực sự có ý nghĩa.
Một dịch vụ mẫu phù hợp
Sẽ thật tuyệt nếu bạn có thể giúp tất cả các developer thực sự dễ dàng tuân theo hầu hết các nguyên tắc mà bạn có với rất ít công việc phải không? Điều gì sẽ xảy ra nếu, ngay từ đầu, các developer đã có hầu hết các mã nguồn để deploy các thuộc tính cốt lõi mà mỗi dịch vụ cần?
Dropwizard và Karyon là hai micro container mã nguồn mở, dựa trên JVM. Chúng hoạt động theo những cách tương tự, tập hợp một bộ các thư viện lại với nhau để cung cấp các tính năng như kiểm tra tình trạng, phục vụ HTTP hoặc hiển thị số liệu. Vì vậy, ngay từ đầu, bạn đã có một dịch vụ hoàn chỉnh với một servlet container có thể được nhúng và khởi chạy từ dòng lệnh. Đây là một cách tuyệt vời để bắt đầu, nhưng tại sao lại dừng lại ở đó? Trong khi bạn đang sử dụng nó, tại sao không lấy một cái gì đó như Dropwizard hoặc Karyon và thêm nhiều tính năng hơn để nó trở nên phù hợp với ngữ cảnh của bạn?
Ví dụ, bạn có thể muốn bắt buộc sử dụng bộ ngắt mạch. Trong trường hợp đó, bạn có thể tích hợp một thư viện như Hystrix. Hoặc bạn có thể có một thực tế rằng tất cả các chỉ số của bạn cần phải được gửi đến một máy chủ Graphite trung tâm, vì vậy có thể kéo thư viện mã nguồn mở như Dropwizard’s Metrics và định cấu hình nó để thời gian phản hồi và tỷ lệ lỗi được đẩy tự động đến một vị trí đã biết.
Bằng cách điều chỉnh một khuôn mẫu dịch vụ như vậy cho tập hợp các phương pháp phát triển của riêng bạn, bạn đảm bảo rằng các nhóm có thể tiến hành nhanh hơn và các developer cũng phải cố gắng làm cho dịch vụ của họ hoạt động dù trong điều kiện không tốt.
Tất nhiên, nếu bạn chấp nhận nhiều stack công nghệ khác nhau, bạn sẽ cần một khuôn mẫu dịch vụ phù hợp cho từng loại. Tuy nhiên, đây có thể là một cách bạn hạn chế một cách tinh vi các lựa chọn ngôn ngữ trong nhóm của mình. Nếu mẫu dịch vụ nội bộ chỉ hỗ trợ Java, thì mọi người có thể không khuyến khích chọn các stack thay thế nếu họ phải tự làm nhiều việc hơn. Netflix, chẳng hạn, đặc biệt quan tâm đến các khía cạnh như khả năng chịu lỗi, để đảm bảo rằng sự cố ngừng hoạt động của một bộ phận trong hệ thống của họ không thể khiến mọi thứ không hoạt động theo. Để xử lý điều này, một lượng lớn công việc đã được thực hiện để đảm bảo rằng có các thư viện trên JVM để cung cấp cho các nhóm các công cụ họ cần để giữ cho các dịch vụ của họ hoạt động tốt. Bất kỳ ai giới thiệu một stack công nghệ mới có nghĩa là phải tái tạo tất cả nỗ lực này. Mối quan tâm chính đối với Netflix không phải là về nỗ lực trùng lặp mà thiên về thực tế là rất dễ mắc phải sai lầm này. Rủi ro mà dịch vụ không thể chịu lỗi khi có nhưng phần được làm mới khi deploy là cao nếu nó có thể ảnh hưởng nhiều hơn đến hệ thống. Netflix giảm thiểu điều này bằng cách sử dụng các dịch vụ sidecar, giao tiếp cục bộ với một JVM đang sử dụng các thư viện thích hợp.
Bạn phải cẩn thận rằng việc tạo khuôn mẫu dịch vụ không trở thành công việc của một nhóm công cụ hoặc một nhóm kiến trúc sư tập trung, những người chỉ định cách mọi thứ nên được thực hiện, mặc dù thông qua mã nguồn. Việc xác định các phương pháp bạn sử dụng phải là một hoạt động tập thể, vì vậy, lý tưởng nhất là (các) nhóm của bạn nên chịu trách nhiệm chung về việc cập nhật khuôn mẫu này (phương pháp tiếp cận nguồn mở nội bộ hoạt động tốt ở đây).
Tôi cũng đã thấy tinh thần và năng suất của nhiều nhóm bị suy giảm nghiêm trọng khi có một framework bị áp đặt sử dụng. Trong nỗ lực cải thiện khả năng tái sử dụng mã nguồn, ngày càng nhiều công việc được đặt vào một framework tập trung cho đến khi nó trở thành một con quái vật khổng lồ. Nếu bạn quyết định sử dụng một khuôn mẫu dịch vụ phù hợp, hãy suy nghĩ thật kỹ về công việc của nó. Lý tưởng nhất, việc sử dụng nó nên hoàn toàn là tùy chọn, nhưng nếu bạn muốn áp dụng nó một cách mạnh mẽ hơn, bạn cần hiểu rằng tính dễ sử dụng cho các developer sẽ là động lực chính.
Cũng cần lưu ý về những nguy cơ khi mã nguồn được chia sẻ lại. Với mong muốn tạo ra mã có thể sử dụng lại, chúng ta có thể đã tạo ra nguồn gốc của sự bó buộc (coupling) giữa các dịch vụ. Ít nhất một tổ chức mà tôi đã nói chuyện với họ, họ lo lắng về điều này đến nỗi nó thực sự đã sao chép mã nguồn khuôn mẫu dịch vụ của mình theo cách thủ công vào mỗi dịch vụ. Điều này có nghĩa là việc nâng cấp lên khuôn mẫu dịch vụ cốt lõi sẽ mất nhiều thời gian hơn để được áp dụng trên toàn hệ thống, nhưng điều này ít liên quan đến nó hơn là nguy cơ của sự bó buộc. Các nhóm khác mà tôi đã nói chuyện chỉ đơn giản coi mẫu dịch vụ là một dependency kiểu nhị phân (đã được compile) được chia sẻ, mặc dù họ phải rất chăm chỉ trong việc không để bị DRY (don’t repeat yourself – đừng lặp lại chính mình) dẫn đến một hệ thống kết hợp quá chặt chẽ với nhau! Đây là một chủ đề mang nhiều sắc thái, vì vậy chúng ta sẽ khám phá chi tiết hơn trong Chương 4.
[note1]: Đây là một nguyên tắc trong việc phát triển phần mềm, nói đến việc không để mã nguồn bị lặp lại. Trong cuốn The Pragmatic Programer thì Dry được định nghĩa như sau: “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”
Nợ kỹ thuật – Technical Debt
Chúng ta thường bị đặt vào những tình huống mà chúng ta không thể theo kịp về tầm nhìn kỹ thuật của chúng ta. Thông thường, chúng ta cần phải lựa chọn cắt một vài góc để có được một số tính năng cấp thiết. Đây chỉ là một sự đánh đổi nữa mà chúng ta sẽ thấy mình phải thực hiện. Tầm nhìn kỹ thuật của chúng ta tồn tại là có lý do. Nếu chúng ta đi chệch khỏi lý do này, nó có thể mang lại lợi ích ngắn hạn nhưng phải trả giá dài hạn. Một khái niệm giúp chúng ta hiểu sự đánh đổi này là nợ kỹ thuật (technical debt). Khi chúng ta tích lũy món nợ này, cũng giống như nợ trong thế giới thực, nó có chi phí liên tục và là thứ chúng ta muốn trả bớt.
Đôi khi nợ kỹ thuật không chỉ là thứ mà chúng ta gây ra bằng cách đi tắt. Điều gì xảy ra nếu tầm nhìn của chúng ta đối với hệ thống thay đổi, nhưng không phải tất cả hệ thống của chúng ta đều phù hợp? Trong tình huống này, chúng tôi đã tạo ra các nguồn nợ kỹ thuật mới.
Công việc của kiến trúc sư hệ thống là nhìn vào bức tranh toàn cảnh hơn và hiểu được sự cân bằng này. Có một số quan điểm về mức độ nợ và những chỗ ảnh hưởng, là điều quan trọng. Tùy thuộc vào tổ chức của bạn, bạn có thể cung cấp một hướng dẫn theo kiểu nhẹ nhàng, và để các nhóm tự quyết định cách theo dõi và thanh toán khoản nợ. Đối với các tổ chức khác, bạn có thể cần phải làm việc có nguyên tắc và cấu trúc hơn, có thể là duy trì việc log những khoản nợ và xem xét thường xuyên.
Xử lý ngoại lệ
Vì vậy, các nguyên tắc và thực tiễn của chúng ta hướng dẫn cách xây dựng hệ thống của chúng ta. Nhưng điều gì xảy ra khi hệ thống của chúng ta đi chệch hướng này? Đôi khi chúng ta đưa ra quyết định chỉ là một ngoại lệ của quy tắc. Trong những trường hợp này, bạn nên ghi lại quyết định như vậy vào log ở đâu đó để tham khảo trong tương lai. Nếu tìm thấy đủ các trường hợp ngoại lệ, chúng ta cũng có thể thay đổi nguyên tắc hoặc thông lệ để phản ánh một cách hiểu mới về thế giới. Ví dụ, chúng tôi có thể có một thông lệ nói rằng chúng ta sẽ luôn sử dụng MySQL để lưu trữ dữ liệu. Nhưng sau đó chúng tôi thấy những lý do thuyết phục để sử dụng Cassandra để lưu trữ có khả năng mở rộng cao, tại thời điểm đó, chúng tôi thay đổi cách nói của mình để nói, “Sử dụng MySQL cho hầu hết các yêu cầu lưu trữ, trừ khi bạn mong đợi sự tăng trưởng lớn về khối lượng dữ liệu, trong trường hợp đó hãy sử dụng Cassandra.”
Tuy nhiên, tôi cần phải nhắc lại rằng mọi tổ chức đều khác nhau. Tôi đã làm việc với một số công ty nơi các nhóm phát triển có mức độ tin cậy và quyền tự chủ cao và ở đó các nguyên tắc rất gọn nhẹ (và nhu cầu xử lý ngoại lệ công khai sẽ giảm đáng kể nếu không bị loại bỏ). Trong các khu tổ chức có cấu trúc hơn, trong đó các developer có ít tự do hơn, việc theo dõi các ngoại lệ có thể rất quan trọng để đảm bảo rằng các quy tắc được đưa ra phản ánh đúng những thách thức mà mọi người đang phải đối mặt. Với tất cả những gì đã nói, tôi là một fan hâm mộ của microservices như một cách tối ưu hóa quyền tự chủ của các nhóm, mang lại cho họ nhiều quyền tự do nhất có thể để giải quyết vấn đề trong tầm tay. Nếu bạn đang làm việc trong một tổ chức đặt ra nhiều hạn chế về cách các developer có thể thực hiện công việc của họ, thì microservices có thể không dành cho bạn.
Quản trị và Lãnh đạo từ Trung tâm
Một phần của những gì kiến trúc sư hệ thống cần xử lý là quản trị. Quản trị mà tôi muốn đề cập đến là gì? Hóa ra Kiểm soát mục tiêu đối với Công nghệ Thông tin và những thứ Liên quan (Control Objectives for Information and Related Technology – COBIT) có một định nghĩa khá hay:
Quản trị đảm bảo rằng các mục tiêu của doanh nghiệp đạt được bằng cách đánh giá các nhu cầu, điều kiện và lựa chọn của các bên liên quan; thiết lập phương hướng thông qua ưu tiên và ra quyết định; và giám sát việc thực hiện, tuân thủ và tiến độ so với chỉ đạo và mục tiêu đã thống nhất.
— COBIT 5
[note2]: đây một framework được đề xuất bởi ISACA (Hiệp hội Kiểm tra và Kiểm soát Hệ thống Thông tin), nhằm giúp các tổ chức đang tìm cách phát triển, triển khai, giám sát và cải thiện quản trị CNTT và quản lý thông tin.
Quản trị có thể áp dụng cho nhiều thứ trong diễn đàn CNTT. Chúng tôi muốn tập trung vào khía cạnh quản trị kỹ thuật, điều mà tôi cảm thấy là công việc của kiến trúc sư. Nếu một trong những công việc của kiến trúc sư là đảm bảo tầm nhìn kỹ thuật, thì quản trị là đảm bảo những gì chúng tôi đang xây dựng phù hợp với tầm nhìn này và phát triển tầm nhìn nếu cần.
Kiến trúc sư chịu trách nhiệm về rất nhiều thứ. Họ cần đảm bảo có một bộ nguyên tắc có thể hướng dẫn sự phát triển và những nguyên tắc này phù hợp với chiến lược của tổ chức. Họ cũng cần đảm bảo rằng những nguyên tắc này không yêu cầu các phương pháp làm việc khiến các developer phải khổ sở vì nó. Họ cần cập nhật công nghệ mới và biết khi nào cần đánh đổi đúng. Đây là một trách nhiệm lớn khủng khiếp. Tất cả những điều đó, và họ cũng cần kéo mọi người theo — nghĩa là, để đảm bảo rằng các đồng nghiệp mà họ đang làm việc hiểu được các quyết định đang được đưa ra và được đưa vào thực tế để thực hiện chúng. Ồ, và như chúng tôi đã đề cập: họ cần dành một chút thời gian với các nhóm để hiểu tác động của các quyết định của họ và thậm chí có thể viết mã nguồn nữa.
Đó là một yêu cầu cao? Chắc chắn rồi. Nhưng tôi chắc chắn với quan điểm rằng họ không nên làm điều này một mình. Một nhóm quản trị hoạt động tốt có thể làm việc cùng nhau để chia sẻ công việc và định hình tầm nhìn.
Thông thường, quản trị là hoạt động của nhóm. Đó có thể là một cuộc trò chuyện thân mật với một nhóm đủ nhỏ hoặc một cuộc họp thường xuyên có cấu trúc hơn với tư cách thành viên nhóm chính thức cho phạm vi lớn hơn. Đây là lúc tôi nghĩ các nguyên tắc mà chúng ta đã đề cập trước đó nên được thảo luận và thay đổi theo yêu cầu. Nhóm này cần được dẫn dắt bởi một tay công nghệ và chủ yếu bao gồm những người đang thực hiện công việc được quản lý. Nhóm này cũng phải chịu trách nhiệm theo dõi và quản lý các rủi ro kỹ thuật.
Một mô hình mà tôi rất thích là có kiến trúc sư chủ trì nhóm, nhưng có phần lớn nhóm được thu hút từ các tay công nghệ của mỗi nhóm deliver sản phẩm — tối thiểu là những nhóm trưởng. Kiến trúc sư chịu trách nhiệm đảm bảo nhóm hoạt động, nhưng toàn bộ nhóm chịu trách nhiệm quản trị. Điều này chia sẻ công việc của kiến trúc sư và đảm bảo rằng có mức độ tham gia cao hơn vào các quyết định từ các nhóm. Nó cũng đảm bảo rằng thông tin luân chuyển tự do từ các nhóm vào toàn bộ tập thể và kết quả là việc đưa ra quyết định trở nên hợp lý và đầy đủ thông tin hơn nhiều.
Đôi khi, nhóm có thể đưa ra quyết định mà kiến trúc sư không đồng ý. Lúc này, kiến trúc sư phải làm gì? Tôi đã gặp tình trạng này trước đây, và có thể nói với bạn đây là một trong những tình huống khó khăn nhất phải đối mặt. Thông thường, tôi thực hiện cách tiếp cận mà tôi nên đi với quyết định của nhóm. Tôi cho rằng tôi đã cố gắng hết sức để thuyết phục mọi người, nhưng cuối cùng thì tôi thấy không đủ thuyết phục. Nhóm thường khôn ngoan hơn nhiều so với từng cá nhân, và tôi đã nhiều lần bị chứng minh là sai! Và hãy tưởng tượng việc một nhóm được cho không gian để đưa ra quyết định, và cuối cùng bị bỏ qua. Nhưng đôi khi tôi đã bác bỏ ý kiến của cả nhóm. Nhưng tại sao, và khi nào? Làm thế nào để bạn chọn cách làm?
Hãy nghĩ đến việc dạy trẻ đi xe đạp. Bạn không thể đi hộ cho họ. Bạn thấy chúng chao đảo, nhưng nếu bạn tham gia vào mỗi lần như thể chúng có thể ngã ra, thì chúng sẽ không bao giờ học được và trong mọi trường hợp, chúng sẽ ngã ra ít hơn bạn nghĩ! Nhưng nếu bạn thấy họ chuẩn bị lao vào dòng xe cộ hoặc vào một cái ao gần đó, thì bạn phải can thiệp. Tương tự như vậy, là một kiến trúc sư, bạn cần phải nắm chắc khi nào, theo nghĩa bóng, nhóm của bạn đang lái vào một cái ao. Bạn cũng cần lưu ý rằng ngay cả khi bạn biết mình đúng và bác bỏ ý kiến của nhóm, điều này có thể làm suy yếu vị trí của bạn và cũng khiến nhóm cảm thấy rằng họ không hề có tiếng nói. Đôi khi điều đúng đắn là đi cùng với một quyết định mà bạn không đồng ý. Biết khi nào nên làm điều này và khi nào không nên làm điều này là khó khăn, nhưng đôi khi đó lại là một điều quan trọng.
Xây dựng đội ngũ
Trở thành người chính chịu trách nhiệm về tầm nhìn kỹ thuật của hệ thống của bạn và đảm bảo rằng bạn đang thực hiện tầm nhìn này không chỉ là việc đưa ra quyết định về công nghệ. Chính những người bạn làm việc cùng sẽ thực hiện công việc. Phần lớn vai trò của người lãnh đạo kỹ thuật là giúp phát triển họ — giúp họ tự hiểu tầm nhìn — và cũng đảm bảo rằng họ cũng có thể là những người tham gia tích cực trong việc định hình và thực hiện tầm nhìn.
Giúp những người xung quanh bạn phát triển sự nghiệp có thể có nhiều hình thức, hầu hết đều nằm ngoài phạm vi của cuốn sách này. Tuy nhiên, có một khía cạnh mà kiến trúc microservice đặc biệt có liên quan. Với các hệ thống lớn hơn, monolithic, có ít cơ hội hơn để mọi người nâng cấp và sở hữu thứ gì đó. Mặt khác, với các microservice, chúng tôi có nhiều mã nguồn cơ sở tự trị và sẽ có các vòng đời độc lập của riêng chúng. Giúp mọi người thăng tiến bằng cách để họ làm chủ các dịch vụ riêng lẻ trước khi nhận thêm trách nhiệm có thể là một cách tuyệt vời để giúp họ đạt được mục tiêu nghề nghiệp của riêng mình, đồng thời giảm bớt gánh nặng cho người phụ trách!
Tôi rất tin tưởng rằng phần mềm tuyệt vời đến từ những con người tuyệt vời. Nếu bạn chỉ lo lắng về khía cạnh công nghệ của phương trình, bạn đang thiếu một nửa bức tranh rồi đó.
Tóm tắt
Để tóm tắt chương này, đây là những gì tôi thấy là trách nhiệm cốt lõi của kiến trúc sư khi phát triển:
Tầm nhìn
Đảm bảo có một tầm nhìn kỹ thuật được truyền đạt rõ ràng cho hệ thống sẽ giúp hệ thống của bạn đáp ứng các yêu cầu của khách hàng và tổ chức của bạn
Đồng cảm
Hiểu tác động của các quyết định của bạn đối với khách hàng và đồng nghiệp của bạn
Sự hợp tác
Tương tác với càng nhiều đồng nghiệp và đồng nghiệp của bạn càng tốt để giúp xác định, tinh chỉnh và thực hiện tầm nhìn
Khả năng thích ứng
Đảm bảo rằng tầm nhìn kỹ thuật thay đổi khi khách hàng hoặc tổ chức của bạn yêu cầu
Quyền tự trị
Tìm sự cân bằng phù hợp giữa tiêu chuẩn hóa và tạo quyền tự chủ cho các nhóm của bạn
Quản trị
Đảm bảo rằng hệ thống đang được deploy phù hợp với tầm nhìn kỹ thuật
Kiến trúc sư của hệ thống khi tiến hóa là người hiểu rằng để đạt được kỳ tích này là một sự cân bằng liên tục. Các lực đẩy luôn thúc đẩy bạn theo cách này hay cách khác, và việc đứng ở đâu để đẩy lùi hoặc đi đâu với dòng chảy thường là điều thường đi liền với kinh nghiệm. Nhưng phản ứng tồi tệ nhất đối với tất cả những lực đẩy chúng ta đến sự thay đổi là trở nên cứng nhắc hoặc cố định hơn trong suy nghĩ của chúng ta.
Mặc dù phần lớn lời khuyên trong chương này có thể áp dụng cho bất kỳ kiến trúc sư của hệ thống nào, nhưng các microservice cho chúng ta nhiều quyết định hơn để đưa ra. Vì vậy, có thể cân bằng tốt hơn tất cả những sự đánh đổi này là điều cần thiết.
Trong chương tiếp theo, chúng ta sẽ đưa ra một số nhận thức mới về vai trò của kiến trúc sư đối với chúng ta khi chúng ta bắt đầu suy nghĩ về cách tìm ra ranh giới phù hợp cho microservices.
Chào các bạn, nếu một ngày đẹp trời bạn nhận được 1 task tích hợp Unity hoặc đơn giản là bạn muốn thử tích hợp Unity vào project IOS thì hãy tham khảo thử bài viết dưới đây nhé !
1. Tạo dự án Unity
Đầu tiên để tích hợp ta cần có một cái project unity, sau đó ta export cái project unity này ra platform IOS, nếu bạn đã có project unity IOS để tích hợp rồi thì có thể bỏ qua bước này nhé
ở đây mình tạo một project unity đơn giản, tiếp theo ta export project này ra platform IOS
bên trong project unity chọn File -> Build Setting
sudo gem install cocoapods
ở đây ta chọn platform IOS -> để setting như hình bên dưới rồi nhấn build đợi một lúc sẽ ra màn hình chọn thư mục để lưu trữ
Tiếp theo ấn Choose project unity sẽ tự export ra một project IOS có tên là unity
Export project Unity ra swift IOS
2. Tạo dự án iOS
ở đây ta tạo một project đơn giản để tích hợp, trong Xcode chọn File -> new project, ở đây mình để tên project là SimpleIOS
sau khi xong các bước trên thì ta có 2 thư mục sau:
unity: được tạo bằng cách export trong project Unity dưới dạng một dự án iOS
SimpleIOS: project IOS chính cần tích hợp
3. Tích hợp Unity với IOS
Tới đây thì ta sẽ tạo một Workspace trong xcode để có thể add 2 project trên để tích hợp,
trong xcode chọn File -> New -> Workspace
ở đây bạn có thể đặt tên trùng với tên project IOS chính của mình hoặc một tên khác bất kỳ, ở đây mình tạo với tên là SimpleSwiftUnity
Lưu ý: nếu project IOS chính của bạn muốn tích hợp đã có Workspace thì có thể dùng luôn không cần tạo thêm một Workspace mới
sau khi tạo xong ta mở SimpleSwiftUnity.xcworkspace lên sau dó kéo tệp SimpleIOS.xcodeproj và Unity-iPhone.xcodeproj và workspace chính
Thêm dự án IOSThêm dự án Unity IOS
tới bước này thì cả 2 SimpleIOS.xcodeproj và Unity-iPhone.xcodeproj đều thuộc 1 workspace
Tiếp theo, nhấp vào dự án SimpleIOS chọn vào tab General cuộn xuống phần Frameworks, Libraries and Embedded Content . Nhấp vào nút + để add một framework mới.
chọn UnityFramework.framework từ trong list và add vào dự án
Tiếp theo, chọn thư mục Data trong Unity-iPhone project. Trong bảng điều khiển bên phải, bạn sẽ thấy phần Target Membership . Bạn cần tích chọn UnityFramework .
Tới đây đã dủ các bước cấu hình, bây giờ mình sẽ thêm một số dòng code để show unity kia lên project IOS chính nhé !
Tạo file UnityEmbeddedSwift.swift trong SimpleIOS project như bên dưới
tiếp theo ta tạo một UI đơn giản để add unity vào đó như hình bên đưới
Oke tới đây là xong, ta chạy thử ứng dụng và xem thành quả nhé ^^
Lưu ý: Đảm bảo rằng bạn chạy ứng dụng trên thiết bị iPhone thực chứ không phải trên máy ảo!
Các bạn cũng thể add unity thành một View Controller show full màn hình, có thể tham khảo các hàm trong UnityEmbeddedSwift ở đây! link source Chúc các bạn thành công !!!
Chào các bạn, là một lập trình viên Android chắc hẳn bạn đã từng sử dụng qua Library (Thư viện) trong Android. Nó có thể là một thư viện mở được chia sẻ trên internet, hoặc một thư viện ra chính bạn tạo ra.
Hôm nay mình sẽ chia sẻ kiến thức nhỏ mà mình biết được liên quan đến việc publish thư viện Android dưới lên Remote (Online) và Local. Với những khái niệm như Publish Library, Local Maven,….
I. Khái niệm
Đầu tiên, Android Library là gì?
Android Library có cấu trúc giống như Module ứng dụng Android.
Android Library có thể bao gồm mọi thứ cần thiết để xây dựng một ứng dụng xây dựng: Source code, Resource file và một Android Manifest.
Thay vì, tệp App build thành APK file, thì Library build thành AAR file(Android Archive) và sử dụng như một phần phụ thuộc của module ứng dụng Android.
II. Sử dụng và Lợi ích
Vậy, khi nào sử dụng Android Library?
Khi sử dụng nhiều ứng dụng mà bạn có một số thành phần giống nhau, chẳng hạn như activity, service hoặc UI layout người dùng.
Một trong những lợi ích của việc sử dụng Library mà chúng ta có thể kể đến như là:
Tăng tốc developemnt time.
Tái sử dụng lại source code được phân chia chức năng cụ thể.
III. Truy cập
Tạo và keep Library trong Project
Tạo and Publish it to global to truy cập chúng một cách remote hoặc share chúng tới các project khác. Một số cách thông dụng để publish Library lên remote. Một số nơi có thể kể tới
Tạo Library:
Để tạp library module bạn làm theo cách sau: Vào File > New > New Module. Trong cửa sổ Create New Module xuất hiện, nhấp vào Thư viện Android, sau đó nhấp vào Tiếp theo. Ở màn hình tạo Module mới xuất hiện, bạn chọn Thư viện Android, sau đó nhấn Next.
Sau khi hoàn tất các thao tác tạo Lib và implement functions cần thiết
Bạn cũng có thể tạo 1 project thuần tuý là thư viện mà không có App Module kèm theo bằng cách đổi plugin trong Gradle file thành
plugins {
id 'com.android.library'
}
Vì bài này tập trung vào các publish Library nên mình chỉ làm một ví dụ đơn giản về nội dung của Library này về chức năng tính toán.
Chào các bạn, là một lập trình viên Android chắc hẳn bạn đã từng sử dụng qua Library (Thư viện) trong Android. Nó có thể là một thư viện mở được chia sẻ trên internet, hoặc một thư viện ra chính bạn tạo ra.
Hôm nay mình sẽ chia sẻ kiến thức nhỏ mà mình biết được liên quan đến việc publish thư viện Android dưới lên Remote (Online) và Local. Với những khái niệm như Publish Library, Local Maven,….
I. Khái niệm
Đầu tiên, Android Library là gì?
Android Library có cấu trúc giống như Module ứng dụng Android.
Android Library có thể bao gồm mọi thứ cần thiết để xây dựng một ứng dụng xây dựng: Source code, Resource file và một Android Manifest.
Thay vì, tệp App build thành APK file, thì Library build thành AAR file(Android Archive) và sử dụng như một phần phụ thuộc của module ứng dụng Android.
II. Sử dụng và Lợi ích
Vậy, khi nào sử dụng Android Library?
Khi sử dụng nhiều ứng dụng mà bạn có một số thành phần giống nhau, chẳng hạn như activity, service hoặc UI layout người dùng.
Một trong những lợi ích của việc sử dụng Library mà chúng ta có thể kể đến như là:
Tăng tốc developemnt time.
Tái sử dụng lại source code được phân chia chức năng cụ thể.
III. Truy cập
Tạo và keep Library trong Project
Tạo and Publish it to global to truy cập chúng một cách remote hoặc share chúng tới các project khác. Một số cách thông dụng để publish Library lên remote. Một số nơi có thể kể tới
Tạo Library:
Để tạp library module bạn làm theo cách sau: Vào File > New > New Module. Trong cửa sổ Create New Module xuất hiện, nhấp vào Thư viện Android, sau đó nhấp vào Tiếp theo. Ở màn hình tạo Module mới xuất hiện, bạn chọn Thư viện Android, sau đó nhấn Next.
Sau khi hoàn tất các thao tác tạo Lib và implement functions cần thiết
Bạn cũng có thể tạo 1 project thuần tuý là thư viện mà không có App Module kèm theo bằng cách đổi plugin trong Gradle file thành
<pre>
plugins {
id ‘com.android.library’
}
</pre>
Vì bài này tập trung vào các publish Library nên mình chỉ làm một ví dụ đơn giản về nội dung của Library này về chức năng tính toán.
Như mọi người đã biết, hầu hết các ứng dụng ngày nay đều hỗ trợ tính năng đa ngôn ngữ, nhằm mục đích tiếp cận được nhiều người dùng hơn, cho mọi người sử dụng ứng dụng dễ dàng hơn. Tuy nhiên hiện nay hầu hết các dự án đều làm một cách tự phát, chưa có một quy trình chuẩn. Điều này làm cho quá trình phát triển sinh ra nhiều bug UI sai Message, sai chính tả, nó làm tốn thời gian không đáng có của các bên.
Trong quá trình làm việc ở các dự án, khi tài liệu được cập nhật đồng nghĩa với việc các Dev phải quay lại kiểm tra các thay đổi message, rồi bắt đầu loay hoay tìm kiếm trong source code để thực hiện thay đổi.
Hay mỗi khi code một màn hình mới việc phải định nghĩa một đống key, định nghĩa enum/constant và copy/paste nó sang các file localization thật nhàm chán và tiềm tàng nhiều rủi ro về lỗi sai chính tả, copy nhầm. Trước đây, có một member trong dự án của mình chỉ vì copy sai text làm thừa một dấu “;” làm lỗi cả file đa ngôn ngữ, việc này làm cho toàn bộ text trên ứng dụng khi release cho bị sai. Do file đa ngôn ngữ là string nên những lỗi sai chính tả như này mất rất nhiều thời gian để điều tra lỗi.
Sau nhiều năm làm việc với các dự án khác nhau, cùng các anh em, cộng sự hoàn thành các ứng dụng to nhỏ khác nhau. Mình đã đúc kết được một quy trình giúp cải thiện năng suất làm việc của mọi người, giúp giảm các bug UI về sai message, sai chính tả, giảm thời gian thực hiện tính năng đa ngôn ngữ, giúp quá trình bảo trì, thay đổi message trở nên dễ dàng hơn.
Quy trình
Brainstoming
Trước tiên, để đạt được hiệu quả Leader của các bên gồm BA, Mobile, Web … ngồi lại với nhau để thống nhất về cách làm việc theo quy trình trên. Sau đó phổ biến lại cho member và đảm bảo việc member thực hiện đúng quy trình làm việc.
Tạo file excel chung lưu các message cần làm đa ngôn ngữ
Trong file này mình có định nghĩa sẵn các trường cần nhập và có sẵn công thức để gen ra code của swift và android. Nếu dự án của các bạn sử dụng ngôn ngữ khác thì có thể chỉnh lại công thức cho phù hợp với dự án của mình.
Nhập nội dung
Thêm/sửa message
Screen/Feature: Điền vào tiên màn hình hoặc tính năng.
MessageKey: Điền vào tên item trên màn hình, nếu SRS có mô tả thì ưu tiên sử dụng key trong SRS, nếu không hãy đặt tên sao cho đúng ý nghĩa của item.
Tiếng việt: Text được hiển thị khi ngôn ngữ là Tiếng Việt
Tiếng anh: Text được hiển thị khi ngôn ngữ là Tiếng Anh
Note: TH1. Tài liệu đã định nghĩa sẵn các message cho các ngôn ngữ: thì BA có thể cử 1 bạn ra điền hết vào file này trước khi bên dev thực hiện code. Trường hợp này thì việc làm đa ngôn ngữ khá nhàn, mình sẽ giải thích chi tiết ở các phần phía dưới. TH2: Tài liệu chưa định nghĩa message cho các ngôn ngữ(các dự án thường rơi vào trường hợp này): Khi này khi các Dev thực hiện code sẽ tự insert thêm message vào file trên teams, để file tự tạo ra code tương ứng với các ngôn ngữ.
Công thức tạo code tự động
Công thức tạo code từ excel
Các Developers sẽ không phải làm các thao tác lặp đi lặp lại nhàm chán. Ngoài ra nó giúp giảm lỗi đánh máy, lỗi copy paste, lỗi sai chính tả và giúp việc làm đa ngôn ngữ nhanh hơn. Trên file excel mình có tạo ra công thức để tự tạo ra code của swift/android. Khi bạn điền thông tin vào sheet MessageList thì code sẽ được gen tự động, việc của mọi người là chỉ cần copy code vào project và sử dụng.
Đưa ra các luật lệ
Tạo ra các luật yêu cầu member phải tuân thủ như sau:
Đặt tên màn hình theo quy định của dự án, cách đặt tên cần thống nhất giữa tài liệu SRS và các class trong code của Dev.
Dev của các bên(mobile, web) và BA khi thực hiện thêm mới, hay chỉnh sửa sẽ thực hiện trực tiếp trên files Localize chung của dự án, khi chỉnh sửa cần tìm đúng tên màn hình, message để sửa, nếu thêm mới thì thêm xuống cuối cùng của của danh sách list message của màn hình đó.
Để tránh việc conflict hoặc giẫm chân nhau khi các bên chạy song song, thì mỗi màn hình những member liên quan tới màn hình đó sẽ cử ra một bạn điển vào file message trước.
Cách thực hiện code
TH1: đối với dự án không cho sử dụng thư viện mã nguồn mở
Khi thực hiện chúng ta chỉ cần vào file excel và copy code vào project của mình rồi sử dụng bình thường.
TH2:Dự án cho phép sử dụng thư viện mã nguồn mở
Chúng ta sẽ sử dụng thư viện để tự tạo ra enum nhanh gọn lẹ hơn, ví dụ bên iOS Swift thì chúng ta có thể sử dụng Swiftgen để thực hiện. Link hướng dẫn sử dụng Swiftgen mình cũng có viết môt bài rồi các bạn có thể tham khảo link dưới đây:
Giảm thời gian làm việc, tăng năng suất làm việc của team
Giảm khả năng bị lỗi UI/Message hay các lỗi về copy/paste khi thực hiện
Giảm thời gian bảo trì ứng dụng khi thay đổi message, khi có thay đổi BA sẽ update file message list và báo lại cho Devs, lúc này Dev không cần phải tìm xem thay đổi ở đâu, mà chỉ cần copy code từ file message list là xong.
Giảm thời gian implement khi có yêu cầu hỗ trợ thêm ngôn ngữ khác. Vì khi này chúng ta chỉ cần thêm 1 cột nữa ở file excel rồi copy code sang project là xong.
Giúp tối ưu được effort khi các team không start cùng một thời điểm. Vì team start sau sẽ sử dụng lại được phần đã làm của team start trước.
Nhược điểm
Cần quản lí, phân chia công việc tốt, member có khả năng làm việc nhóm.
Thường chỉ hiệu quả cao đối với các dự án mới
Tổng kết
Phía trên là nội dung chia sẻ về cách tối ưu khi làm ứng dụng hỗ trợ đa ngôn ngữ, mình hi vọng bài viết có thể giúp mọi người phần nào trong quá trình thực hiện các ứng dụng có hỗ trợ đa ngôn ngữ. Cảm ơn mọi người đã dành thời gian đọc bài viết này.
Tìm hiểu cách mà SwiftGen giúp dễ dàng loại bỏ các chuỗi ma thuật(magic strings) trong các dự án iOS của bạn.
Là một nhà phát triển ứng dụng trên thiết bị di động, bạn có thể cảm thấy như bạn gặp phép thuật hàng ngày. Các dòng mã bạn viết sẽ được chuyển đổi thành các ứng dụng mà mọi người trên khắp thế giới có thể sử dụng. Các công cụ mà Apple cung cấp giúp biến điều kỳ diệu đó thành hiện thực và làm cho cuộc sống của bạn dễ dàng hơn. Khi tiến xa hơn vào lĩnh vực phát triển phần mềm, bạn có thể nhận ra có một thứ ma thuật mà bạn không thích: Chuỗi thần kỳ(magic strings).
Loại an toàn, khái niệm rằng các biến chỉ có thể thuộc một loại cụ thể, cung cấp cho các nhà phát triển các rào chắn(guardrails) để giữ cho các chương trình của họ an toàn. Tuy nhiên, chuỗi ma thuật đưa mã không an toàn vào các ứng dụng đó. Chuỗi ma thuật là gì? Trong quá trình phát triển iOS, bạn đã gặp phải những điều này nhiều lần. Một ví dụ trông giống như sau:
let color = UIColor(named: "green-apple")
self.title = "Welcome!"
Ví dụ này hiển thị “green-apple” và “Welcome!” được viết dưới dạng chuỗi trực tiếp trong mã của bạn. Không quá khi nói rằng tất cả các nhà phát triển đôi khi nhận thấy mình có lỗi với hành vi này.
Trên thực tế, trong quá trình phát triển iOS, bạn không có nhiều lựa chọn. Ngoài ra, Xcode không cung cấp cách nào để tránh việc này.
Những người đã làm việc trong Android có thể thấy mình run sợ trước những đoạn mã như thế này. Môi trường phát triển Android có cơ chế chuyển đổi tài nguyên ứng dụng, chẳng hạn như chuỗi, màu sắc, hình ảnh và phông chữ, thành các kiểu biến an toàn. Có rất nhiều lợi ích từ việc này, và đó là:
Giảm lỗi sai chính tả
Ngăn chặn sự trùng lặp tài nguyên không cần thiết
Cung cấp kiểm tra tài nguyên tại thời điểm biên dịch
Giúp dọn dẹp tài nguyên cũ
Và nhiều hơn thế nữa
Như đã nêu, các nhà phát triển iOS và macOS không có quyền truy cập vào hệ thống cung cấp sự an toàn cho loại tài nguyên này.
May mắn thay, có SwiftGen, một trình tạo mã để loại bỏ các chuỗi ma thuật trong ứng dụng của bạn. Có sẵn dưới dạng thư viện mã nguồn mở trên GitHub, bạn có thể thêm tính năng này vào các dự án iOS và macOS của mình để mang lại sự an toàn về loại và kiểm tra thời gian biên dịch của tất cả các Assets của bạn.
Trong hướng dẫn này bạn sẽ học được cách:
Thiết lập dự án của bạn với SwiftGen
Xác định các nội dung mà bạn muốn chuyển đổi
Xác định nơi mà code được generated
Tạo các templates cho phép SwiftGen generate khi dùng SwiftUI cho Fonts và màu sắc.
Getting Started
Để bắt đầu, hãy nhấp vào nút Tải xuống tài liệu dưới đây:
Có một số cách bạn có thể cài đặt SwiftGen để hoạt động với môi trường của mình như sau:
CocoaPods
Homebrew
Mint
Directly download a zipped release(Nên dùng cách này)
Link hướng dẫn cài ở đây: https://github.com/SwiftGen/SwiftGen
Trong hướng dẫn này, bạn sẽ sử dụng CocoaPods để quản lý SwiftGen.
Lưu ý: Nếu bạn không có CocoaPods, đừng lo lắng – dự án khởi động và dự án cuối cùng đã được tải xuống phần phụ thuộc. :]
Mở workspace, có tên là DrinksUp! .Xcworkspace. Vì dự án này sử dụng CocoaPods nên bạn sẽ không thể làm việc trực tiếp với DrinksUp! .Xcodeproj.
Hãy dành một chút thời gian để xem xét xung quanh trong Xcode. Dự án đã ở trạng thái hoàn thành nhưng sử dụng chuỗi để tham chiếu phông chữ, màu sắc, hình ảnh và chuỗi. Bạn sẽ chuyển đổi tất cả những thứ này vào cuối hướng dẫn.
Xây dựng và chạy và làm quen với ứng dụng.
Ứng dụng, DrinksUp !, là một cách để theo dõi các loại đồ uống thú vị mà bạn và gia đình đã thử khi đến nhà hàng hoặc ở nhà.
Thiết lập SwiftGen
Bắt đầu bằng cách mở Terminal và điều hướng đến thư mục gốc của dự án khởi động của bạn. Tiếp theo, nhập lệnh sau vào Terminal:
./Pods/SwiftGen/bin/swiftgen config init
Thao tác này sẽ tạo một tệp cấu hình, có tên là swiftgen.yml, tại thư mục gốc dự án của bạn. Nếu tệp này tự động mở trong Xcode, hãy tiếp tục và tắt nó đi.
Tiếp theo, trong workspace dự án của bạn, đi tới Files ▸ Add new file vào “DrinksUp!”…. Tìm swiftgen.yml. Đảm bảo bỏ chọn Sao chép các mục nếu cần và chọn Tạo tham chiếu thư mục.
Nhấp vào nút Thêm. Khi hoàn tất, bạn sẽ thấy swiftgen.yml ở đầu trình điều hướng Dự án, như bên dưới:
Lưu ý: Bạn có thể di chuyển tệp này đến vị trí giống như hình minh họa, nếu Xcode không thêm nó theo cách tương tự.
Tệp này là nơi bạn sẽ đặt các hướng dẫn cho SwiftGen biết tệp nào bạn muốn chuyển đổi thành mã được tạo. Loại tệp, YML , cho biết nó đang sử dụng YAML cho cú pháp của nó. Nếu bạn chưa sử dụng YAML trước đây, đây chỉ đơn giản là một cách dễ đọc hơn để xem dữ liệu được tuần tự hóa. Bạn có thể nghĩ về nó như là JSON , được đơn giản hóa.
Bây giờ, thay thế toàn bộ nội dung của swiftgen.yml bằng nội dung sau:
Bạn đã khai báo một biến input_dir thư mục đầu vào. Điều này cho SwiftGen biết thư mục gốc để điều hướng đến tất cả các đường dẫn tệp mà bạn sẽ sớm thêm vào.
Một biến khác xác định thư mục đầu ra của các tệp Swift được tạo. Bằng cách này, bạn sẽ dễ dàng theo dõi tất cả các tệp SwiftGen hơn.
Thêm Build Phase
Để chạy SwiftGen, bạn sẽ cần thêm một giai đoạn xây dựng mới vào dự án của mình. Để thực hiện việc này, hãy chọn dự án của bạn trong trình điều hướng Dự án, chọnBuild Phases. Chọn + và chọnNew Run Script Phase.
Đổi tên script thành SwiftGen bằng cách nhấp đúp vào tên hiện tại, Run Script . Tiếp theo, thêm phần sau vào trường văn bản của tập lệnh:
if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]]; then
"${PODS_ROOT}/SwiftGen/bin/swiftgen"
else
echo "warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
fi
Cuối cùng, sắp xếp lại thứ tự tập lệnh để ngồi ngay sau tên tập lệnh [CP] Check Pods Manifest.lock . Các Build Phases của bạn bây giờ sẽ giống như sau:
Build và Run. Nếu mọi thứ được thiết lập đúng cách, bạn sẽ không có bất kỳ lỗi nào. Bạn sẽ không có bất kỳ thứ gì trong thư mục Đã tạo của mình. Được rồi chúng ta đến bước tiếp theo.
Chuyển đổi XCAssets
Bây giờ, bạn đã sẵn sàng để bắt đầu xóa các chuỗi khỏi dự án của mình! Bước đầu tiên sẽ là SwiftGen tạo mã cho các tệp XCAsset trong dự án. Mở swiftgen.yml và thêm phần sau vào cuối tệp:
Đây là ý nghĩa của mỗi dòng trong số những dòng này:
Mỗi loại tệp hoặc mẫu, bạn muốn chuyển đổi bằng SwiftGen yêu cầu một mục nhập ở cấp gốc là swiftgen.yml . Ở đây, điều này cho biết bạn muốn SwiftGen chuyển đổi các tệp là XCAsset .
Danh sách này cho biết những tệp SwiftGen nên giới hạn chuyển đổi của nó.
Bạn cần cho SwiftGen biết cách tạo đầu ra.
Bạn phải cung cấp tên mẫu. Ở đây, swift5 là một mẫu mặc định được cung cấp bởi nhóm SwiftGen. Bạn sẽ học cách sử dụng các mẫu của riêng mình sau này.
Cuối cùng, bạn cung cấp tên tệp mà bạn muốn mã Swift mới của mình tạo ra. Hãy nhớ rằng, bạn đã xác định output_dir ở đầu tệp, có nghĩa là nó sẽ xuất thành Generated / XCAssets + Generated.swift .
Build và Run. Nếu bạn không gặp bất kỳ lỗi nào, thì quá trình tạo mã của bạn đã hoạt động!
Thêm tệp
Mở rộng folder Generated trong trình Project navigator. Hiện tại, bạn vẫn sẽ không tìm thấy tệp mới của mình. Để thêm nó, nhấp chuột phải vào Generated và chọn Add Files to “DrinksUp!”… .
Chọn XCAssets + Generated.swift . Đảm bảo Copy items if needed không được chọn, sau đó nhấp vào Add . Bây giờ, hãy mở XCAssets + Generate.swift và quan sát. Bạn sẽ thấy enumAsset. Trong enum, bạn sẽ tìm thấy các bảng liệt kê khác được xác định phù hợp với các danh mục XCAsset mà bạn đã xác định. Ví dụ:
Assets: Mỗi hình ảnh trong dự án bây giờ có một thuộc tính tĩnh được xác định.
Colors: Tất cả các màu của ứng dụng cũng có các thuộc tính tĩnh để tham khảo.
Mở Assets.xcassets . Lưu ý rằng có một nhóm hình ảnh có tên là nội dung khởi chạy . Nhưng Assets đã khai báo tất cả các thuộc tính ảnh tĩnh ở cùng một mức. SwiftGen có thể duy trì tổ chức này cho bạn nhưng không làm như vậy theo mặc định. Mở swiftgen.yml và thay thế toàn bộ mục nhập xcassets bằng mục sau:
Tại đây, bạn tận dụng khả năng của SwiftGen để tùy chỉnh đầu ra mã của bạn bằng cách thực hiện như sau:
Xác định params trên outputs của bạn.
forceProvidesNamespaces: Điều này sẽ duy trì không gian tên của bạn được tìm thấy trong danh mục nội dung.
Tham số bổ sung này đảm bảo cho dù bạn đã cung cấp bao nhiêu tên tệp inputs, SwiftGen sẽ duy trì các bảng liệt kê riêng biệt để đại diện cho từng danh mục nội dung.
Xây dựng dự án, sau đó quay lại XCAssets + Generated.swift . Bây giờ bạn sẽ thấy Assets có một enum tên mới LaunchAssets để đại diện cho cấu trúc thư mục của bạn.
Bây giờ, đã đến lúc sử dụng mã mới được tạo này để xóa bất kỳ tham chiếu chuỗi nào đến hình ảnh. Mở DrinksListView.swift . Bạn sẽ thấy Image("milkshake")bên trong các mục trên thanh công cụ được thêm vào chế độ xem. Thay thế dòng bằng dòng sau:
Image(Asset.Assets.milkshake.name)
Ở đây, bạn đã tham chiếu tên hình ảnh cho milkshake. Hiện tại, SwiftGen không hỗ trợ làm việc trực tiếp với SwiftUI. Bạn sẽ học cách tự thêm cái này sau. Hiện tại, bạn vẫn có thể sử dụng những gì có sẵn để tải nội dung hình ảnh mà không cần tham chiếu trực tiếp đến chuỗi.
Sử dụng các mẫu cơ bản bổ sung
Có một số mẫu bổ sung mà bạn có thể tận dụng mà không cần tùy chỉnh.
Làm việc với trình tạo giao diện
Ứng dụng bạn đang làm việc đang sử dụng SwiftUI. Tuy nhiên, để giới thiệu khả năng của SwiftGen để làm việc với Trình tạo giao diện, dự án mẫu bao gồm một bảng phân cảnh và một số bộ điều khiển chế độ xem. Bắt đầu bằng cách tạo mã để hỗ trợ Trình tạo giao diện hoặc Bảng phân cảnh bằng cách thêm phần sau vào swiftgen.yml :
Cho SwiftGen biết rằng bạn muốn nó tìm kiếm bất kỳ tệp nào được hỗ trợ trình tạo giao diện trong thư mục gốc của dự án của bạn.
Một điều tuyệt vời về SwiftGen là nó tách biệt khái niệm Cảnh khỏi Segues . Điều này cho biết tệp mà cảnh của bạn sẽ xuất ra.
Cuối cùng, điều này cho biết tất cả thông tin giả mạo sẽ được xuất ra ở đâu.
Xây dựng dự án của bạn. Thêm IB-Scenes + Generate.swift và IB-Segues + Generated.swift vào nhóm Generated , giống như bạn đã làm đối với XCAssets + Generated.swift .
Giờ đây, bạn có thể thay thế thông tin cảnh hoặc thông tin xác thực trong ứng dụng.
Bắt đầu bằng cách mở InformationViewController.swift . Trong InformationViewController, thay thế việc triển khai showAbout()bằng những điều sau:
Tất cả các segues sẽ tạo ra dưới dạng các enum trường hợp thực tế, điều này giúp mọi thứ dễ dàng hơn khi bạn kiểm tra xem segue nào được kích hoạt. Vì đơn giản, mã bạn đã thêm chỉ đang kích hoạt segue.
Tiếp theo, bên trong InformationView, thay thế makeUIViewController(context:)bằng những thứ sau:
func makeUIViewController(context: Context) -> some UIViewController {
StoryboardScene.Main.initialScene.instantiate()
}
Ở đây, bạn đã đơn giản hóa mã cần thiết để khởi tạo bộ điều khiển chế độ xem ban đầu của bảng phân cảnh. SwiftGen cung cấp một phương thức trợ giúp để nhanh chóng truy cập cảnh ban đầu của bạn.
Xây dựng và chạy. Nhấn vào Tìm hiểu thêm ở trên cùng bên phải. Bạn sẽ thấy một phương thức xuất hiện, hiển thị cho bạn một số thông tin về ứng dụng. Điều này được gọi vào makeUIViewController(context:)để biết chế độ xem nào cần tải.
Bây giờ, hãy nhấn vào nút About . Bạn sẽ kích hoạt segue mà bạn đã sửa đổi.
Làm việc với JSON
Cuối cùng chúng ta sẽ thêm phần hỗ trợ cho JSON. Thêm phần sau vào cuối swiftgen.yml :
Điều này hiện cung cấp một cách để tham chiếu đến bất kỳ tệp JSON nào bạn có trong tài nguyên của dự án. Bạn sẽ sử dụng điều này để chuyển đổi dữ liệu giả đi kèm với ứng dụng.
Xây dựng dự án, sau đó thêm JSON + Generated.swift vào nhóm Đã tạo của bạn .
Bây giờ, hãy mở Drink.swift . Loại bỏ hoàn toàn các struct tên được đặt tên MockData. Sau đó, thay thế phần mở rộng cho DrinkStore, được tìm thấy ở dưới cùng, bằng phần mở rộng sau:
extension DrinkStore {
static var mockData: [Drink] {
do {
let data = try JSONSerialization.data(
withJSONObject: JSONFiles.starterDrinks,
options: [])
let mockDrinks = try JSONDecoder().decode([Drink].self, from: data)
return mockDrinks
} catch {
print(error.localizedDescription)
return []
}
}
}
Tại đây, bạn sẽ thấy các tham chiếu mã của mình JSONFiles.starterDrinks. Mở MockData.json và nhận thấy khóa đầu tiên trong tệp, có tên là starterDrinks . SwiftGen đã lấy đối tượng cấp cao nhất này và cung cấp nó dưới dạng thuộc tính tĩnh trên JSONFiles để bạn tham khảo khi cần.
Xây dựng và chạy. Bạn sẽ không nhận thấy bất cứ điều gì khác so với trước đây – chỉ là đồ uống hiển thị trong danh sách.
Làm việc với chuỗi
Có lẽ một trong những tiện ích lớn nhất mà SwiftGen cung cấp là khả năng sử dụng các biến để tham chiếu các chuỗi được bản địa hóa(Localized Strings). Đó là một thực tiễn tuyệt vời để đặt bất kỳ văn bản nào bạn sẽ trình bày cho người dùng ứng dụng của mình bên trong các tệp strings hoặc tệp stringsdict được bản địa hóa . Nhưng nếu bạn đã làm điều này, bạn biết rằng một khi bạn vượt quá một số chuỗi, sẽ trở nên khó khăn để nhớ những chuỗi nào có sẵn. Cũng có cảm giác thừa rằng bạn có một chuỗi trong tệp chuỗi và… một chuỗi trong mã của bạn.
Dự án này chứa các tệp chuỗi sau:
Localizable.strings : Tệp chuỗi run of the-mill của bạn, được tạo bằng các khóa và giá trị.
Localizable.stringsdict : Bạn nên sử dụng các tệp stringdict bất cứ khi nào bạn cần lo lắng về các chuỗi đa nguyên. Loại tệp này không chỉ hỗ trợ dịch các chuỗi mà còn hỗ trợ cách đa hóa các từ cho bất kỳ biến thể nào mà một ngôn ngữ yêu cầu.
Để chuyển đổi tất cả các tệp chuỗi của bạn, hãy thêm phần sau vào swiftgen.yml :
Bạn nên biết một số điều quan trọng về những gì bạn đã thêm ở đây:
Khi bạn chuyển đổi các tệp chuỗi của mình, bạn chỉ nên sử dụng một trong các thư mục được bản địa hóa. Đối với mỗi ngôn ngữ được thêm vào, một thư mục bản địa hóa mới sẽ được tạo. Trong dự án này, bản địa hóa duy nhất là tiếng Anh. Nếu bạn thêm nhiều ngôn ngữ hơn, không cần phải sửa đổi mục nhập này để nhận các bản dịch bổ sung đó. Bởi vì tệp chuỗi phải có một tập hợp các khóa và giá trị phù hợp, bạn sẽ tham chiếu các bản dịch giống như bạn sẽ làm nếu bạn không sử dụng SwiftGen.
Bạn đã thêm một tham số mới, được đặt tên publicAccess. Nếu bạn nhìn xung quanh bất kỳ tệp nào đã tạo mà bạn đã thêm, bạn sẽ thấy tất cả các loại đều có công cụ sửa đổi quyền truy cập internal. Bằng cách sử dụng tham số này, bạn có thể thay đổi công cụ sửa đổi quyền truy cập của các bảng kê đã tạo thành công khai.
Build dự án, sau đó thêm Strings + Generated.swift vào nhóm Generated . Trước khi bạn chuyển đổi tất cả các chuỗi trong ứng dụng, điều quan trọng là phải hiểu tệp này hơi khác một chút như thế nào.
Hiểu tệp
Mở Strings + Generated.swift . Bạn sẽ thấy kiểu cha được tạo, được đặt tên L10n. Trong phần này enum, bạn sẽ thấy một số kiểu con được tạo bên trong nó DrinkDetail: Navigationvà DrinkList. Chúng tương ứng với các chuỗi được khai báo trong Localizable.strings .
Mở Localizable.strings và xem cách nó khai báo mục nhập đầu tiên:
"DrinkList.Navigation.Title" = "Drinks";
Lưu ý cách khai báo khóa bằng ký hiệu không gian tên bằng dấu chấm:
DrinkList : Điều này cho biết chuỗi này thuộc về màn hình Danh sách đồ uống.
Navigation : Cho biết chuỗi này sẽ được sử dụng trong thanh điều hướng.
Title : Cuối cùng, điều này cho biết đó là tiêu đề trong thanh điều hướng.
Bây giờ, bạn có thể chọn tổ chức và đặt tên cho các chuỗi của mình theo cách khác. Không có gì sai với điều đó – kiểu đặt tên này được sử dụng để hiển thị cách SwiftGen sẽ chuyển đổi và tổ chức mã của bạn. Đối với mỗi khoảng thời gian bạn đặt trong chuỗi của mình, SwiftGen sẽ tạo thêm một kiểu con.
Quay lại Strings + Generated.swift , bạn sẽ thấy hàm tĩnh drinksCount. SwiftGen giúp bạn dễ dàng làm việc với các chuỗi đa nguyên. Thay vì phải tạo tham chiếu đến các chuỗi được bản địa hóa và sử dụng trình định dạng chuỗi, các hàm được tạo này giúp bạn dễ dàng sử dụng một hàm lấy các giá trị của chuỗi đa nguyên của bạn.
Bây giờ, chuyển đổi tất cả các chuỗi bản địa hóa được sử dụng trong ứng dụng để trỏ đến các loại được tạo. Bắt đầu bằng cách mở DrinksListView.swift . Tiếp theo, tìm dòng mã:
Text("DrinkList.Navigation.Title")
Đổi nó thành
Text(L10n.DrinkList.Navigation.title)
Chờ một chút… L10n là gì? Đây là một phím tắt cho “bản địa hóa”. Bạn cũng có thể thấy “quốc tế hóa” được viết tắt là i18n . Nếu bạn đếm các chữ cái giữa chữ cái đầu tiên và chữ “n” cuối cùng trong một trong hai từ, bạn sẽ tìm thấy 10 hoặc 18 chữ cái tương ứng. Mặc dù điều này có ý nghĩa, sẽ không tốt nếu sử dụng một tên khác cho loại chuỗi cấp cao nhất của bạn phải không?
Mở swiftgen.yml và thêm một thuộc tính vào mục nhập chuỗi của bạn, ngay sau publicAccessđó, nó trông giống như sau:
Ở đây, bạn đã thêm tham số enumName. Điều này cho phép bạn thay đổi loại từ “L10n” thành “Strings”.
Xây dựng và chạy. Lần này, bạn sẽ có một lỗi biên dịch. Điều này là do loại L10n không còn nữa. Truy cập DrinksListView.swift và tìm:
Text(L10n.DrinkList.Navigation.title)
Thay thế nó bằng
Text(Strings.DrinkList.Navigation.title)
Bây giờ, ứng dụng của bạn đang sử dụng tên loại mới mà bạn đã cung cấp ở bước trước.
Build ứng dụng của bạn. Bạn sẽ không còn gặp bất kỳ lỗi biên dịch nào nữa.
Lưu ý : Nếu bạn vẫn gặp lỗi, bạn có thể cần phải làm sạch dự án của mình. Chọn Product ▸ Clean and build folders , sau đó tạo lại.
Tiếp theo tìm tài sản drinkCountString. Đây là mã sử dụng tệp stringdict để xử lý cách hiển thị số lượng đồ uống trong danh sách. Thay thế nó bằng những thứ sau:
private var drinkCountString: String {
Strings .drinksCount (drinkStore.drinks.count)
}
Nếu bạn so sánh nó với mã ở đó trước đây, bạn có thể thấy đây là cách nhanh hơn nhiều để tham chiếu các chuỗi đa nguyên.
Bạn nên chuyển đổi tất cả các chuỗi trong dự án khỏi sử dụng chuỗi. Mở Localizable.strings và xem tất cả các khóa chuỗi. Bạn nên tìm từng cách sử dụng các khóa này trong một tệp Swift và hoán đổi nó cho các biến được tạo bởi SwiftGen.
Khi bạn hoán đổi văn bản xếp hạng trong DrinkDetailView.swift , nó sẽ sử dụng một hàm để cung cấp chuỗi, giống như cách bạn xử lý số lượng đồ uống.
Tạo mẫu tùy chỉnh
Cho đến thời điểm này, tệp swiftgen.yml của bạn đã sử dụng các mẫu mặc định do SwiftGen cung cấp. Tất cả những thứ này được lưu trữ trong SwiftGen pod. Nếu các mẫu này không cung cấp đầy đủ chức năng bạn muốn, bạn có thể tạo các mẫu của riêng mình để tạo mã theo cách bạn muốn. Các mẫu được xây dựng bằng Stencil , một dự án mã nguồn mở cung cấp ngôn ngữ tạo mẫu cho Swift. Trong phần này, bạn sẽ học cách sửa đổi các mẫu hiện có và sử dụng chúng để tạo mã của bạn.
Nếu bạn nhìn trong trình điều hướng Dự án, bạn sẽ thấy có một thư mục có tên là Mẫu . Trong đó, có hai thư mục con: Fonts và xcassets . Với những điều này, bạn sẽ được SwiftGen cung cấp hỗ trợ để sử dụng màu sắc và phông chữ trực tiếp trong SwiftUI.
Hỗ trợ màu SwiftUI
Để thêm Colorhỗ trợ SwiftUI, hãy mở asset_swift5_swiftui.stencil . Lúc đầu, mọi thứ có thể hơi choáng ngợp vì tệp không có bất kỳ hỗ trợ cú pháp mã nào.
Trên dòng 13, thêm dòng mã sau:
import SwiftUI
Tiếp theo, quay lại swiftgen.yml . Trong mục nhập đầu tiên của bạn, đối với xcassets , hãy tìm dòng nơi bạn xác định tên mẫu:
Tại đây, bạn đã thay đổi từ sử dụng templateNamesang templatePath. Điều này yêu cầu SwiftGen sử dụng mẫu tùy chỉnh của bạn thay vì mẫu tích hợp sẵn.
Xây dựng dự án, sau đó truy cập XCAssets + Generated.swift . Ở trên cùng, bây giờ bạn sẽ thấy:
import SwiftUI
Vì bạn đã thêm nhập vào tệp Stencil nên khi mã được tạo, mã sẽ chọn thay đổi này và thêm nhập mới. Khá tuyệt, phải không?
Mở asset_swift5_swiftui.stencil và thay thế:
// Add Support For SwiftUI Here
Như dưới đây:
{{accessModifier}} private(set) lazy var color: Color = {
Color(systemColor)
}()
Trong đoạn mã này, bạn có thể xem cách sử dụng Stencil nhiều hơn một chút. Đây là những gì bạn đã thêm:
{{accessModifier}}: Đây là cách bạn nói với Stencil cách thay thế bằng thứ gì đó được cung cấp trong quá trình tạo mã. Nếu bạn nhìn vào dòng 11 của tệp này, bạn sẽ thấy accessModifier được định nghĩa là một biến. Theo mặc định, công cụ sửa đổi quyền truy cập là internal. Nếu bạn nhớ từ trước, bạn đã thấy cách bạn có thể thay đổi điều này thànhpublic
Phần còn lại của điều này thực sự chỉ là mã tiêu chuẩn. Nó tạo ra màu SwiftUI từ màu UIKit.
Lưu ý : Có một sửa đổi khác được thực hiện đối với mẫu như một phần của vật liệu khởi động. Nó thay đổi loại Colorthành SystemColor.
Build lại dự án, sau đó quay lại XCAssets + Generated.swift . Trên dòng 62, bạn sẽ thấy mã từ mẫu của mình, hiện được tạo dưới dạng mã thực.
Bây giờ, bạn cần hoán đổi bất kỳ tham chiếu được mã hóa cứng nào thành một màu để sử dụng chức năng mới của mình. Mở DrinksListView.swift và tìm vị trí đặt màu nền trước trên văn bản tiêu đề điều hướng. Thay thế nó như sau:
Đây systemColor là tham chiếu đến loại màu cụ thể của nền tảng. Sẽ như UIColor vậy nếu bạn đang sử dụng iOS và NSColor nếu bạn đang sử dụng macOS.
Hai tệp có màu được khai báo bằng cách sử dụng chuỗi:
AppMain.swift
DrinkListView.swift
Hoàn tất chuyển đổi các màu còn lại từ việc sử dụng các chuỗi trong mỗi tệp này theo ví dụ trên.
Hỗ trợ Phông chữ SwiftUI
Cũng như SwiftUI Color hiện không được hỗ trợ trong SwiftGen, Font cũng không được hỗ trợ. Bắt đầu bằng cách mở font_swift5_swiftui.stencil và thêm nhập sau vào đầu tệp:
import SwiftUI
Tiếp theo, thay thế khối mã này được tìm thấy gần cuối tệp:
// Add Support For SwiftUI here
fileprivate extension Font {
}
Bằng đoạn code dưới đây:
fileprivate extension Font {
// 1
static func mappedFont(_ name: String, textStyle: TextStyle) -> Font {
let fontStyle = mapToUIFontTextStyle(textStyle)
let fontSize = UIFont.preferredFont(forTextStyle: fontStyle).pointSize
return Font.custom(name, size: fontSize, relativeTo: textStyle)
}
// 2
static func mapToUIFontTextStyle(
_ textStyle: SwiftUI.Font.TextStyle
) -> UIFont.TextStyle {
switch textStyle {
case .largeTitle:
return .largeTitle
case .title:
return .title1
case .title2:
return .title2
case .title3:
return .title3
case .headline:
return .headline
case .subheadline:
return .subheadline
case .callout:
return .callout
case .body:
return .body
case .caption:
return .caption1
case .caption2:
return .caption2
case .footnote:
return .footnote
@unknown default:
fatalError("Missing a TextStyle mapping")
}
}
}
Đây là những gì bạn đã thêm:
mappedFont(_:textStyle:)tạo một phông chữ tùy chỉnh từ tên và TextStyle. Kiểu được sử dụng để lấy kích thước phông chữ tiêu chuẩn, mặc định cho mỗi phông chữ.
mapToUIFontTextStyle(_:)chỉ đơn giản là cung cấp ánh xạ 1: 1 của SwiftUI TextStyletới UIKitTextStyle
Khối mã này tương tự như những gì bạn đã thêm để cung cấp Color hỗ trợ. Sự khác biệt duy nhất ở đây là nó dành riêng cho việc cung cấp phông chữ trực tiếp trong SwiftUI.
Ứng dụng này sử dụng hai phông chữ, cả hai đều nằm trong nhóm Tài nguyên :
NotoSans
NotoSans-Bold
Mục nhập mới này chỉ cần biết thư mục mẹ của phông chữ bạn muốn hỗ trợ ở đâu. Mọi thứ khác tương tự như tất cả các mục nhập khác mà bạn đã thêm trước đó.
Xây dựng ứng dụng của bạn và thêm Fonts+Generated.swift vào Generated . Sau khi thực hiện, hãy mở Fonts+Generated.swift . Tại đây, bạn có thể thấy cách tổ chức họ phông chữ. Bạn sẽ thấy rằng NotoSans có sẵn các biến thể sau:
Thường
In đậm
In đậm nghiêng
In nghiêng
Giống như tất cả các mã được tạo khác trong ứng dụng, nó khá dễ sử dụng. Mở AppMain.swift và thay thế dòng đầu tiên application(_:didFinishLaunchingWithOptions:)bằng dòng sau:
let buttonFont = FontFamily.NotoSans.bold.font(size: 16)
Tại đây, bạn đặt trực tiếp kích thước phông chữ thành phông chữ NotoSans Bold(in đậm) .
Tiếp theo, truy cập DrinksListView.swift , tìm tham chiếu đầu tiên đến một phông chữ, trong NavigationLink và thay thế nó bằng như sau:
Tại đây, bạn tận dụng mã của mẫu tùy chỉnh của mình để có thể tạo phông chữ SwiftUI tùy chỉnh, kích thước phù hợp với mặc định TextStyle: trong trường hợp này body.
Cuối cùng, hoàn tất việc chuyển đổi tất cả các cách sử dụng của phông chữ trong toàn bộ ứng dụng. Trong cả DrinksListView.swift và DrinkDetailView.swift , bạn sẽ tìm thấy một số nơi đặt phông chữ. Theo ví dụ trên, bạn có thể chuyển đổi mã từ việc sử dụng một chuỗi sang trọng số thích hợp của NotoSans . Mỗi vị trí trong số này đã cho biết TextStylechúng nên có vị trí nào.
Build và Run. Ứng dụng của bạn trông vẫn giống như cách nó hoạt động khi bạn bắt đầu. Nhưng bây giờ bạn sẽ có tất cả các tài nguyên được tham chiếu theo cách an toàn về kiểu loại!
Tổng kết
Bây giờ bạn có thể sử dụng SwiftGen để:
Loại bỏ nhu cầu sử dụng chuỗi để tham chiếu tài nguyên trong ứng dụng của bạn, cho dù bạn sử dụng SwiftUI hay UIKit.
Tùy chỉnh các tệp đầu ra bằng cách sử dụng các thông số cài sẵn.
Sử dụng các mẫu của riêng bạn để tạo mã.
Để tìm hiểu thêm về nó, hãy xem SwiftGen trên GitHub . Bạn cũng có thể tìm hiểu thêm về Stencil trên GitHub .
Chúng tôi hy vọng bạn thích hướng dẫn này. Nếu bạn có bất kỳ câu hỏi hoặc ý kiến nào, hãy tham gia thảo luận của diễn đàn bên dưới!
Nội dung bài viết được dịch từ link: https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios
Trong thời đại công nghệ 4.0, các công ty đua nhau chuyển đổi số, vì vậy có rất nhiều những ứng dụng di động được phát triển để giúp tiếp cận người dùng một cách dễ dàng hơn. Để những ứng dụng có thể vươn xa ra tầm thế giới, tiếp cận được với những người dùng nước ngoài, thì ứng dụng đó cần phải hỗ trợ đa ngôn ngữ. Vì vậy hôm nay mình sẽ hướng dẫn các bạn một số cách thực hiện một ứng dụng iOS hỗ trợ đa ngôn ngữ.
Cài đặt dự án hỗ trợ đa ngôn ngữ
Bước 1: Thực hiện thêm ngôn ngữ hỗ trợ bằng cách Chọn Project -> Info(thông tin) -> Bấm nút +
Thêm ngôn ngữBỏ tích ở các file storyboard để xCode không gen ra các file String cho các files storyboard
Bước 2: Tạo file String để chứa nội dung theo các ngôn ngữ New File… -> tìm string và chọn Strings File -> Next
Bước 3: Localize file string vừa mới tạo Chọn File vừa tạo -> Bấm vào nút Localize… -> Popup hiển thị lên thì chọn Localize
Bước 4: Tích vào ngôn ngữ bạn hỗ trợ, Chọn File String localize vừa được tạo -> Ở menu bên phải mục Localization tích vào những ngôn ngữ mà ứng dụng của bạn hỗ trợ.
Chọn ngôn ngữ hỗ trợ
Vậy là việc cài đặt đa ngôn ngữ cho ứng dụng của bạn đã hoàn thành.
Thực hiện đa ngôn ngữ với các chuỗi (Localize String)
Để tránh các lỗi sai chính tả và việc thực hiện đa ngôn ngữ trở nên dễ dàng hơn thì chúng ta sẽ tạo ra một enum Localization để liệt kê các item/string dưới dạng keyword để sử dụng như sau:
enum Localization {
static let helloWorld: String = "Hello"
static let buttonChangeLanguageTitle: String = "btn.changeLanguage"
}
Với mỗi 1 key của string chúng ta cần tạo ra value tương ứng với nó ở trong file Localizable mà chúng ta đã tạo.
Thêm key/value cho các file ngôn ngữ tương ứng:
Thêm key/value cho ngôn ngữ Tiếng AnhThêm key/value cho ngôn ngữ Tiếng Việt
NOTE: – Kết thúc của dòng code phải là dấu chấm phẩy “;” nếu 1 dòng code bị thiếu nó sẽ khiến ứng dụng của bạn hiển thị không đúng ngôn ngữ. – Key phải trùng với giá trị của enum Localization
2. Tạo một file chung để quản lí ngôn ngữ như đoạn code dưới đây
// for manage language
class LanguageManager {
static let shared = LanguageManager()
private init(){}
// save language to UserDefault
func changeLanguage(_ language: Language) {
UserDefaults.standard.set(language.rawValue, forKey: "APP_LANGUAGE")
}
/// Get language of set on app, if nil use device language
/// - Returns: current language of application
func getAppLanguage() -> Language {
if let language = UserDefaults.standard.value(forKey: "APP_LANGUAGE") as? String {
return Language(rawValue: language) ?? .english
} else {
// default lan is english
let currentLanguage: String = Locale.current.languageCode ?? Language.english.rawValue
return Language(rawValue: currentLanguage) ?? .english
}
}
// define enum language
enum Language: String {
case vietnamese = "vi"
case english = "en"
}
}
Ở đây mình tạo ra một file quản lí ngôn ngữ nhằm mục đích tập trung tất cả những tính năng liên quan tới ngôn ngữ: thay đổi, lấy ra ngôn ngữ …
Chúng ta cần lưu ngôn ngữ của ứng dụng vào Local Storage để khi người dùng tắt ứng dụng vào lại thay đổi vẫn được áp dụng. Trong trường hợp này mình chọn cách dùng UserDefault vì những lí do sau: – Dễ sử dụng – Dữ liệu cần lưu không yêu cầu bảo mật – Dữ liệu lưu có dung lượng nhỏ
3. Vậy là chúng ta đã tạo xong file quản lí ngôn ngữ. Tiếp đến để việc sử dụng localization dễ dàng chúng ta sẽ tạo ra một var localized trong extension String như sau:
Sau khi tạo xong extension thì việc thực hiện code localized trở nên rất dễ dàng. Khi cần sử dụng chúng ta chỉ cần .localized là xong.
4. Demo và cách sử dụng:
Trong ví dụ này mình sẽ tạo ra 1 label hiển thị text và một nút để thay đổi ngôn ngữ của ứng dụng. Sau đó mình tạo ra một func setDataForUI() nhằm mục đích set tất cả các data liên quan tới localization ở đây. Nếu sau đó đổi ngôn ngữ ta chỉ cần gọi lại hàm này để thực hiện set lại.
Vậy là chúng ta đã hoàn thành việc thực hiện localize String cho ứng dụng. Mình hi vọng bài viết này có thể giúp được chút gì đó cho các bạn. Cảm ơn các bạn đã đọc bài viết này, chúc các bạn thành công!