Vì cuộc đời quá ngắn ngủi để vẽ lại sơ đồ
Gần đây tôi đã gia nhập một công ty mới với tư cách là Kỹ sư phần mềm . Như mọi khi, tôi phải bắt đầu lại từ đầu. Những thứ như: Mã cho một ứng dụng đang hoạt động ở đâu? Làm thế nào nó được triển khai? Các cấu hình đến từ đâu? Rất may, các đồng nghiệp của tôi đã làm rất tốt khi biến mọi thứ thành 'cơ sở hạ tầng dưới dạng mã'. Vì vậy, tôi chợt nghĩ: Nếu mọi thứ đều nằm trong mã, tại sao không có công cụ nào để kết nối tất cả các dấu chấm?
Công cụ này sẽ xem xét cơ sở mã và xây dựng sơ đồ kiến trúc ứng dụng, nêu bật các khía cạnh chính. Một kỹ sư mới có thể nhìn vào sơ đồ và nói: “À, được rồi, đây là cách nó hoạt động.”
Cho dù tôi có tìm kiếm kỹ đến đâu, tôi cũng không thể tìm thấy thứ gì như thế. Kết quả phù hợp nhất mà tôi tìm thấy là các dịch vụ vẽ sơ đồ cơ sở hạ tầng. Tôi đưa một số trong số chúng vào bài đánh giá này để bạn có thể xem xét kỹ hơn. Cuối cùng, tôi đã từ bỏ việc tìm kiếm trên Google và quyết định thử phát triển một số nội dung thú vị mới.
Đầu tiên, tôi xây dựng một ứng dụng Java mẫu với Gradle, Docker và Terraform. Quy trình hành động của GitHub triển khai ứng dụng trên Amazon Elastic Container Service. Kho lưu trữ này sẽ là nguồn cho công cụ tôi sẽ xây dựng (mã ở đây ).
Thứ hai, tôi đã vẽ một sơ đồ cấp cao về kết quả mà tôi muốn thấy:
Tôi quyết định sẽ có hai loại tài nguyên:
Tôi thấy thuật ngữ tạo tác quá tải nên tôi đã chọn Relic . Vậy Di tích là gì? Đó là 90% những gì bạn muốn xem. Bao gồm nhưng không giới hạn trong:
Mỗi Relic đều có tên (ví dụ: my-shiny-app), loại tùy chọn (ví dụ: Jar) và một tập hợp các cặp khóa → giá trị (ví dụ: đường dẫn → /build/libs/my-shiny-app.jar) mô tả đầy đủ Relic. Chúng được gọi là Định nghĩa . Di tích càng có nhiều Định nghĩa - càng tốt.
Loại thứ hai là Nguồn . Các nguồn xác định, xây dựng hoặc cung cấp Di tích (ví dụ: hộp màu vàng ở trên). Nguồn mô tả Di tích ở một nơi nào đó và cho biết nó đến từ đâu. Mặc dù Nguồn là thành phần mà chúng tôi nhận được nhiều thông tin nhất nhưng chúng thường có ý nghĩa phụ trên sơ đồ. Bạn có thể không cần nhiều mũi tên đi từ Terraform hoặc Gradle đến mọi Di tích khác.
Relic và Source có mối quan hệ nhiều-nhiều.
Bao gồm mọi đoạn mã là không thể. Các ứng dụng hiện đại có thể có nhiều khung, công cụ hoặc thành phần đám mây. Chỉ riêng AWS đã có khoảng 950 tài nguyên và nguồn dữ liệu cho Terraform! Công cụ này phải dễ dàng mở rộng và tách rời theo thiết kế để những người hoặc công ty khác có thể đóng góp.
Mặc dù tôi là một người rất hâm mộ kiến trúc của các nhà cung cấp Terraform có khả năng cắm cực kỳ cao, nhưng tôi đã quyết định xây dựng kiến trúc tương tự, mặc dù đã được đơn giản hóa:
Nhà cung cấp có một trách nhiệm rõ ràng: xây dựng Di tích dựa trên các tệp nguồn được yêu cầu. Ví dụ: GradleProvider đọc các tệp *.gradle và trả về Jar , War hoặc Gz Relics. Mỗi nhà cung cấp xây dựng Di tích theo loại mà họ biết. Nhà cung cấp không quan tâm đến sự tương tác giữa các Di tích. Họ xây dựng Di tích một cách khai báo, hoàn toàn tách biệt với nhau.
Với cách tiếp cận đó, thật dễ dàng để đi sâu như bạn muốn. Một ví dụ điển hình là GitHub Actions. Tệp YAML quy trình công việc điển hình bao gồm hàng chục bước sử dụng các thành phần và dịch vụ được liên kết lỏng lẻo. Một quy trình công việc có thể xây dựng một JAR, sau đó là hình ảnh Docker và triển khai nó vào môi trường. Mỗi bước trong quy trình làm việc đều có thể được nhà cung cấp của nó thực hiện. Vì vậy, giả sử các nhà phát triển Docker Actions tạo một Nhà cung cấp chỉ liên quan đến các bước họ quan tâm.
Cách tiếp cận này cho phép bất kỳ số lượng người nào làm việc song song, bổ sung thêm tính logic cho công cụ. Người dùng cuối cũng có thể nhanh chóng triển khai Nhà cung cấp của họ (trong trường hợp một số công nghệ độc quyền). Xem thêm trong phần Tùy chỉnh bên dưới.
Hãy cùng xem xét cái bẫy tiếp theo trước khi đi vào phần hấp dẫn nhất. Hai Nhà cung cấp, mỗi nhà cung cấp tạo ra một Thánh tích. Tốt rồi. Nhưng điều gì sẽ xảy ra nếu hai trong số các Thánh tích này chỉ là biểu diễn của cùng một thành phần được xác định ở hai nơi? Đây là một ví dụ.
AmazonECSProvider phân tích định nghĩa tác vụ JSON và tạo ra Relic với loại AmazonECSTask . Quy trình hành động GitHub cũng có một bước liên quan đến ECS, do đó, một nhà cung cấp khác sẽ tạo Bản di tích triển khai AmazonECSTask . Bây giờ, chúng tôi có các bản sao vì cả hai nhà cung cấp đều không biết gì về nhau. Hơn nữa, việc bất kỳ ai trong số họ cho rằng người khác đã tạo ra Thánh tích là không chính xác. Rồi sao?
Chúng tôi không thể loại bỏ một trong hai bản sao vì Định nghĩa (thuộc tính) mà mỗi bản sao có. Cách duy nhất là hợp nhất chúng. Theo mặc định, logic tiếp theo xác định quyết định hợp nhất:
relic1.name() == relic2.name() && relic1.source() != relic2.source()
Chúng tôi hợp nhất hai Thánh tích nếu tên của chúng bằng nhau nhưng chúng được xác định trong các Nguồn khác nhau (như trong ví dụ của chúng tôi, JSON trong kho lưu trữ và tham chiếu định nghĩa tác vụ nằm trong Hành động GithHub).
Khi hợp nhất, chúng tôi:
Tôi đã cố tình bỏ qua một khía cạnh quan trọng của Thánh tích. Nó có thể có Matcher — và tốt hơn là nên có nó! Matcher là một hàm boolean nhận một đối số và kiểm tra nó. Bộ so khớp là phần quan trọng của quá trình liên kết. Nếu Thánh tích khớp với bất kỳ định nghĩa nào về Thánh tích của người khác, chúng sẽ được liên kết với nhau.
Bạn có nhớ khi tôi nói rằng Nhà cung cấp không biết gì về Di tích do Nhà cung cấp khác tạo ra không? Điều đó vẫn đúng. Tuy nhiên, Nhà cung cấp xác định Trình so khớp cho Di tích. Nói cách khác, nó đại diện cho một bên của mũi tên giữa hai hộp trên sơ đồ kết quả.
Ví dụ. Dockerfile có lệnh ENTRYPOINT.
ENTRYPOINT java -jar /app/arch-diagram-sample.jar
Với một chút chắc chắn, chúng ta có thể nói rằng Docker chứa bất cứ thứ gì được chỉ định trong ENTRYPOINT . Vì vậy, Dockerfile Relic có chức năng Matcher đơn giản: entrypointInstruction.contains(anotherRelicsDefinition)
. Rất có thể, một số Di tích Jar có arch-diagram-sample.jar
trong Định nghĩa sẽ khớp với nó. Nếu có, một mũi tên giữa Dockerfile và Jar Relics sẽ xuất hiện.
Với Matcher được xác định, quá trình liên kết trông khá đơn giản. Dịch vụ liên kết lặp lại trên tất cả các Di tích và gọi các chức năng của Trình so khớp chúng. Thánh tích A có khớp với bất kỳ định nghĩa B nào của Thánh tích không? Đúng? Thêm một cạnh giữa các Thánh tích đó trong biểu đồ kết quả. Cạnh cũng có thể được đặt tên.
Bước cuối cùng là hình dung biểu đồ cuối cùng của chúng ta về giai đoạn trước. Ngoài PNG rõ ràng, công cụ này còn hỗ trợ các định dạng bổ sung, chẳng hạn như Nàng tiên cá , UML thực vật và DOT . Những định dạng văn bản này có thể trông kém hấp dẫn hơn, nhưng lợi thế rất lớn là bạn có thể nhúng những văn bản đó vào hầu hết mọi trang wiki (
Đây là sơ đồ cuối cùng của repo mẫu:
Khả năng cắm các thành phần tùy chỉnh hoặc điều chỉnh logic hiện có là điều cần thiết, đặc biệt khi một công cụ đang ở giai đoạn đầu. Di tích và Nguồn theo mặc định đủ linh hoạt; bạn có thể đặt bất cứ điều gì bạn muốn vào chúng. Mọi thành phần khác đều có thể tùy chỉnh. Các nhà cung cấp hiện tại không cung cấp đủ nguồn lực bạn cần? Thực hiện của riêng bạn một cách dễ dàng. Không hài lòng với logic hợp nhất hoặc liên kết được mô tả ở trên? Không có gì; thêm LinkStrategy hoặc MergeStrategy của riêng bạn. Đóng gói mọi thứ vào một tệp JAR và thêm nó khi khởi động. Đọc thêm tại đây .
Việc tạo sơ đồ dựa trên mã nguồn có thể sẽ thu hút được sự chú ý. Và đặc biệt là công cụ NoReDraw (vâng, đây là tên của công cụ tôi đang nói đến). Những người đóng góp được chào đón !
Lợi ích đáng chú ý nhất (xuất phát từ cái tên) là không cần phải vẽ lại sơ đồ khi các thành phần thay đổi. Việc thiếu sự quan tâm kỹ thuật là lý do khiến tài liệu nói chung (và sơ đồ nói riêng) trở nên lỗi thời. Với các công cụ như NoReDraw , điều này sẽ không còn là vấn đề nữa vì nó có thể dễ dàng kết nối với bất kỳ quy trình PR/CI nào. Hãy nhớ rằng, cuộc sống quá ngắn để vẽ lại sơ đồ 😉