paint-brush
앱 아키텍처 다이어그램 자동화: 소스에서 코드베이스를 매핑하는 도구를 구축한 방법~에 의해@vladimirf
29,858 판독값
29,858 판독값

앱 아키텍처 다이어그램 자동화: 소스에서 코드베이스를 매핑하는 도구를 구축한 방법

~에 의해 Vladimir Filipchenko6m2024/07/30
Read on Terminal Reader

너무 오래; 읽다

코드를 즉시 명확하고 시각적인 다이어그램으로 바꿀 수 있는 도구가 있었으면 좋겠다고 생각한 적이 있습니까? 글쎄, 그것이 바로 NoReDraw가 하는 일입니다! 소프트웨어 엔지니어의 좌절감에서 탄생한 이 도구는 아티팩트 및 구성과 같은 주요 구성 요소를 식별하고 이를 서로 연결하여 포괄적인 아키텍처 다이어그램을 만듭니다. 이는 매우 사용자 정의가 가능하고 쉽게 확장 가능하도록 설계되어 내용이 변경될 때마다 다이어그램을 다시 그리는 번거로움 없이 문서를 최신 상태로 유지할 수 있습니다.
featured image - 앱 아키텍처 다이어그램 자동화: 소스에서 코드베이스를 매핑하는 도구를 구축한 방법
Vladimir Filipchenko HackerNoon profile picture
0-item

다이어그램을 다시 그리기에는 인생이 너무 짧기 때문에


저는 최근에 소프트웨어 엔지니어 로 새로운 회사에 합류했습니다. 항상 그렇듯이 처음부터 다시 시작해야 했습니다. 예: 앱의 코드가 어디에 있나요? 어떻게 배포되나요? 구성은 어디에서 제공되나요? 고맙게도 동료들은 모든 것을 '코드로서의 인프라'로 만드는 환상적인 일을 해냈습니다. 그래서 나는 생각했습니다. 모든 것이 코드에 있다면 모든 점을 연결하는 도구는 왜 없을까?


이 도구는 코드베이스를 검토하고 주요 측면을 강조하는 애플리케이션 아키텍처 다이어그램을 구축합니다. 새로운 엔지니어는 다이어그램을 보고 "아, 알겠습니다. 이것이 작동하는 방식입니다."라고 말할 수 있습니다.


먼저 첫 번째 것들

아무리 검색해도 그런 내용은 찾을 수 없었습니다. 내가 찾은 가장 가까운 일치 항목은 인프라 다이어그램을 그리는 서비스였습니다. 좀 더 자세히 살펴보실 수 있도록 그 중 일부를 이번 리뷰 에 포함시켰습니다. 결국 저는 인터넷 검색을 포기하고 새롭고 멋진 것들을 개발해 보기로 결정했습니다.


먼저 Gradle, Docker 및 Terraform을 사용하여 샘플 Java 앱을 구축했습니다. GitHub 작업 파이프라인은 Amazon Elastic Container Service에 앱을 배포합니다. 이 저장소는 제가 만들 도구의 소스가 될 것입니다(코드는 여기에 있습니다).


둘째, 결과로 보고 싶은 내용에 대한 매우 높은 수준의 다이어그램을 그렸습니다.



나는 두 가지 유형의 리소스가 있다고 결정했습니다.

유물

인공물 이라는 용어가 너무 과부화되어 Relic 을 선택했습니다. 그렇다면 유물이란 무엇입니까? 보고 싶은 것의 90%가 전부입니다. 다음을 포함하되 이에 국한되지는 않습니다.

  • 아티팩트(구성표의 파란색 상자, 즉 Jar, Docker 이미지)
  • Terraform 리소스 구성(구성표의 분홍색 상자, 즉 EC2 인스턴스, ECS, SQS 대기열)
  • 쿠버네티스 리소스,
  • 그리고 훨씬 더 많은 것


모든 Relic에는 이름(예: my-shiny-app), 선택적 유형(예: Jar) 및 키 → 값 쌍 세트(예: 경로 → /build/libs/my-shiny-app.jar)가 있습니다. 유물을 완전히 설명합니다. 이를 정의 라고 합니다. Relic의 정의가 많을수록 좋습니다.

원천

두 번째 유형은 Source 입니다. 소스는 유물(예: 위의 노란색 상자)을 정의, 구축 또는 제공합니다. 소스는 어떤 장소의 유물을 설명하고 그것이 어디서 왔는지에 대한 감각을 제공합니다. 소스는 우리가 가장 많은 정보를 얻는 구성 요소이지만 일반적으로 다이어그램에서는 부차적인 의미를 갖습니다. Terraform이나 Gradle에서 다른 모든 Relic으로 이동하는 데 많은 화살표가 필요하지 않을 것입니다.


Relic과 Source는 다대다 관계를 가지고 있습니다.


분열시켜 정복하라

모든 코드를 다루는 것은 불가능합니다. 최신 앱에는 다양한 프레임워크, 도구 또는 클라우드 구성 요소가 있을 수 있습니다. AWS에만 Terraform용 리소스와 데이터 소스가 약 950개 있습니다! 이 도구는 다른 사람이나 회사가 기여할 수 있도록 설계상 쉽게 확장하고 분리할 수 있어야 합니다.


나는 믿을 수 없을 만큼 플러그 가능한 Terraform 제공업체 아키텍처의 열렬한 팬이지만 단순화되었지만 동일한 아키텍처를 구축하기로 결정했습니다.

공급자


공급자에게는 요청된 소스 파일을 기반으로 Relic을 구축하는 한 가지 명확한 책임이 있습니다. 예를 들어 GradleProvider는 *.gradle 파일을 읽고 Jar , War 또는 Gz Relics를 반환합니다. 각 공급자는 자신이 알고 있는 유형의 유물을 구축합니다. 공급자는 Relic 간의 상호 작용에 관심이 없습니다. 그들은 선언적으로 서로 완전히 격리된 유물을 구축합니다.


이러한 접근 방식을 사용하면 원하는 만큼 깊이 들어가기가 쉽습니다. 좋은 예는 GitHub Actions입니다. 일반적인 워크플로 YAML 파일은 느슨하게 결합된 구성 요소와 서비스를 사용하는 수십 개의 단계로 구성됩니다. 워크플로는 JAR을 빌드한 다음 Docker 이미지를 빌드하고 이를 환경에 배포할 수 있습니다. 워크플로의 모든 단일 단계는 해당 공급자가 처리할 수 있습니다. 따라서 Docker Actions 의 개발자는 관심 있는 단계에만 관련된 공급자를 만듭니다.


이 접근 방식을 사용하면 여러 사람이 동시에 작업할 수 있어 도구에 더 많은 논리가 추가됩니다. 최종 사용자는 (일부 독점 기술의 경우) 공급자를 신속하게 구현할 수도 있습니다. 아래의 사용자 정의에서 자세한 내용을 확인하세요.


병합할 것인지, 병합하지 않을 것인지

가장 흥미로운 부분으로 들어가기 전에 다음 함정을 살펴보겠습니다. 두 개의 공급자, 각각 하나의 Relic을 생성합니다. 괜찮아. 하지만 이러한 Relic 중 두 개가 두 위치에 정의된 동일한 구성 요소를 표현한 것이라면 어떻게 될까요? 여기에 예가 있습니다.


AmazonECSProvider는 작업 정의 JSON을 구문 분석하고 AmazonECSTask 유형의 Relic을 생성합니다. GitHub 작업 워크플로에도 ECS 관련 단계가 있으므로 다른 공급자가 AmazonECSTaskDeployment Relic을 생성합니다. 이제 두 공급자 모두 서로에 대해 아무것도 모르기 때문에 중복이 있습니다. 더욱이, 다른 사람이 이미 유물을 만들었다고 가정하는 것은 올바르지 않습니다. 그리고 뭐?


유물 병합


각각의 정의(속성) 때문에 중복된 항목을 삭제할 수 없습니다. 유일한 방법은 그것들을 병합하는 것입니다. 기본적으로 다음 논리는 병합 결정을 정의합니다.


 relic1.name() == relic2.name() && relic1.source() != relic2.source()


이름이 동일하지만 서로 다른 소스에 정의된 경우 두 개의 Relic을 병합합니다(예: 저장소의 JSON 및 작업 정의 참조는 GitHub 작업에 있음).


병합할 때 우리는 다음을 수행합니다.

  1. 단일 이름을 선택하세요.
  2. 모든 정의 병합(키 → 값 쌍)
  3. 두 원본 소스를 모두 참조하는 복합 소스 생성


선을 그리다

나는 유물의 중요한 측면 중 하나를 의도적으로 생략했습니다. Matcher가 있을 수 있으며, 갖는 것이 더 좋습니다! Matcher는 인수를 가져와 테스트하는 부울 함수입니다. 일치자는 연결 프로세스의 중요한 부분입니다. 유물이 다른 유물의 정의와 일치하면 서로 연결됩니다.


제공자는 다른 제공자가 만든 유물에 대한 단서가 없다고 말한 것을 기억하십니까? 그것은 여전히 사실입니다. 그러나 공급자는 Relic에 대한 Matcher를 정의합니다. 즉, 결과 다이어그램의 두 상자 사이에 있는 화살표의 한 쪽을 나타냅니다.


유물 일치


예. Dockerfile에는 ENTRYPOINT 명령어가 있습니다.


 ENTRYPOINT java -jar /app/arch-diagram-sample.jar


어느 정도 확실하게 말하면 Docker는 ENTRYPOINT 아래에 지정된 모든 것을 컨테이너화한다고 말할 수 있습니다. 따라서 Dockerfile Relic에는 간단한 Matcher 함수( entrypointInstruction.contains(anotherRelicsDefinition) 가 있습니다. 아마도 정의에 있는 arch-diagram-sample.jar 있는 일부 Jar Relics가 이에 일치할 것입니다. 그렇다면 DockerfileJar Relics 사이에 화살표가 나타납니다.


Matcher를 정의하면 연결 프로세스가 매우 간단해 보입니다. 연결 서비스는 모든 Relic을 반복하고 해당 Matcher의 기능을 호출합니다. Relic A는 Relic의 B 정의와 일치합니까? 예? 결과 그래프에서 해당 유물 사이에 가장자리를 추가합니다. 가장자리에 이름을 붙일 수도 있습니다.


심상

마지막 단계는 이전 단계의 최종 그래프를 시각화하는 것입니다. 명백한 PNG 외에도 이 도구는 Mermaid , Plant UMLDOT 와 같은 추가 형식을 지원합니다. 이러한 텍스트 형식은 덜 매력적으로 보일 수 있지만 가장 큰 장점은 해당 텍스트를 거의 모든 위키 페이지에 삽입할 수 있다는 것입니다( GitHub , 합류 , 그리고 더 많은).


샘플 저장소의 최종 다이어그램은 다음과 같습니다.

최종 다이어그램


맞춤화

사용자 지정 구성 요소를 연결하거나 기존 논리를 조정하는 기능은 특히 도구가 초기 단계에 있을 때 필수적입니다. 유물과 소스는 기본적으로 충분히 유연합니다. 당신이 원하는 것은 무엇이든 넣을 수 있습니다. 다른 모든 구성 요소는 사용자 정의할 수 있습니다. 기존 제공업체가 귀하에게 필요한 리소스를 제공하지 않습니까? 쉽게 직접 구현해 보세요. 위에서 설명한 병합 또는 연결 논리가 만족스럽지 않습니까? 괜찮아요; 자신만의 LinkStrategy 또는 MergeStrategy를 추가하세요. 모든 것을 JAR 파일로 압축하고 시작 시 추가합니다. 여기에서 자세한 내용을 읽어보세요.


아웃트로

소스 코드를 기반으로 다이어그램을 생성하면 관심을 끌 가능성이 높습니다. 특히 NoReDraw 도구(예, 이것이 제가 얘기했던 도구의 이름입니다). 기여자들을 환영합니다 !


이름에서 비롯된 가장 주목할만한 이점은 구성 요소가 변경될 때 다이어그램을 다시 그릴 필요가 없다는 것입니다. 엔지니어링에 대한 관심 부족으로 인해 일반적인 문서(특히 다이어그램)가 시대에 뒤떨어지게 됩니다. NoReDraw 와 같은 도구를 사용하면 모든 PR/CI 파이프라인에 쉽게 연결할 수 있으므로 더 이상 문제가 되지 않습니다. 다이어그램을 다시 그리기에는 인생이 너무 짧다는 것을 기억하세요 😉