Статьи

Архитектура программного обеспечения как код

Если вы следили за блогом, вы недавно видели пару постов о согласовании архитектуры программного обеспечения и кода. Архитектура программного обеспечения против кода говорит о типичном разрыве между тем, как мы думаем об архитектуре программного обеспечения, и о коде, который мы пишем, в то время как архитектурно-очевидный стиль кодирования показывает пример того, как обеспечить, чтобы код действительно отражал эти архитектурные концепции. Основная сводка истории на данный момент заключается в том, что все становится намного проще для понимания, если ваши архитектурные идеи просто и явно отображаются в коде.

Регулярные читатели также узнают, что я большой поклонник использования диаграмм для визуализации и передачи архитектуры программной системы, и такое представление о «общем изображении» мира часто трудно увидеть из тысяч строк кода, которые делают до наших программных систем. Одна из вещей, которой я учу людей во время моих семинаров по рисованию, — это как набросать программную систему с помощью небольшого числа простых диаграмм, каждая на очень разных уровнях абстракции. Это основано на моей модели C4 , с которой вы можете ознакомиться в разделе Простые наброски для построения диаграмм архитектуры вашего программного обеспечения., Отзывы людей, использующих эту модель, были отличными, и у многих есть дополнительный вопрос «какой инструмент вы бы порекомендовали?». Мой ответ обычно был «Visio или OmniGraffle», но очевидно, что здесь есть возможность.

Представление модели архитектуры программного обеспечения в коде

За последние несколько месяцев у меня было много разных идей о том, как создать, по сути, легкий инструмент моделирования, и по какой-то причине все эти идеи собрались на прошлой неделе, когда я был на конференции GOTO в Амстердаме. Я не уверен, почему, но у меня было несколько разговоров, которые вдохновляли меня по-разному, поэтому я пропустил одну из бесед, чтобы собрать код и проверить некоторые идеи. Это в основном то, что я придумал …

Model model = new Model();
 
SoftwareSystem techTribes = model.addSoftwareSystem(Location.Internal, "techtribes.je", "techtribes.je is the only way to keep up to date with the IT, tech and digital sector in Jersey and Guernsey, Channel Islands");
 
Person anonymousUser = model.addPerson(Location.External, "Anonymous User", "Anybody on the web.");
Person aggregatedUser = model.addPerson(Location.External, "Aggregated User", "A user or business with content that is aggregated into the website.");
Person adminUser = model.addPerson(Location.External, "Administration User", "A system administration user.");
 
anonymousUser.uses(techTribes, "View people, tribes (businesses, communities and interest groups), content, events, jobs, etc from the local tech, digital and IT sector.");
aggregatedUser.uses(techTribes, "Manage user profile and tribe membership.");
adminUser.uses(techTribes, "Add people, add tribes and manage tribe membership.");
 
SoftwareSystem twitter = model.addSoftwareSystem(Location.External, "Twitter", "twitter.com");
techTribes.uses(twitter, "Gets profile information and tweets from.");
 
SoftwareSystem gitHub = model.addSoftwareSystem(Location.External, "GitHub", "github.com");
techTribes.uses(gitHub, "Gets information about public code repositories from.");
 
SoftwareSystem blogs = model.addSoftwareSystem(Location.External, "Blogs", "RSS and Atom feeds");
techTribes.uses(blogs, "Gets content using RSS and Atom feeds from.");
 
Container webApplication = techTribes.addContainer("Web Application", "Allows users to view people, tribes, content, events, jobs, etc from the local tech, digital and IT sector.", "Apache Tomcat 7.x");
Container contentUpdater = techTribes.addContainer("Content Updater", "Updates profiles, tweets, GitHub repos and content on a scheduled basis.", "Standalone Java 7 application");
Container relationalDatabase = techTribes.addContainer("Relational Database", "Stores people, tribes, tribe membership, talks, events, jobs, badges, GitHub repos, etc.", "MySQL 5.5.x");
Container noSqlStore = techTribes.addContainer("NoSQL Data Store", "Stores content from RSS/Atom feeds (blog posts) and tweets.", "MongoDB 2.2.x");
Container fileSystem = techTribes.addContainer("File System", "Stores search indexes.", null);
 
anonymousUser.uses(webApplication, "View people, tribes (businesses, communities and interest groups), content, events, jobs, etc from the local tech, digital and IT sector.");
authenticatedUser.uses(webApplication, "Manage user profile and tribe membership.");
adminUser.uses(webApplication, "Add people, add tribes and manage tribe membership.");
 
webApplication.uses(relationalDatabase, "Reads from and writes data to");
webApplication.uses(noSqlStore, "Reads from");
webApplication.uses(fileSystem, "Reads from");
 
contentUpdater.uses(relationalDatabase, "Reads from and writes data to");
contentUpdater.uses(noSqlStore, "Reads from and writes data to");
contentUpdater.uses(fileSystem, "Writes to");
contentUpdater.uses(twitter, "Gets profile information and tweets from.");
contentUpdater.uses(gitHub, "Gets information about public code repositories from.");
contentUpdater.uses(blogs, "Gets content using RSS and Atom feeds from.");

It’s a description of the context and container levels of my C4 model for the techtribes.je system. Hopefully it doesn’t need too much explanation if you’re familiar with the model, although there are some ways in which the code can be made simpler and more fluent. Since this is code though, we can easily constrain the model and version it. This approach works well for the high-level architectural concepts because there are very few of them, plus it’s hard to extract this information from the code. But I don’t want to start crafting up a large amount of code to describe the components that reside in each container, particularly as there are potentially lots of them and I’m unsure of the exact relationships between them.

Scanning the codebase for components

If your code does reflect your architecture (i.e. you’re using an architecturally-evident coding style), the obvious solution is to just scan the codebase for those components, and use those to automatically populate the model. How do we signify what a «component» is? In Java, we can use annotations…

package je.techtribes.component.tweet;

import com.structurizr.annotation.Component;
...

@Component(description = "Provides access to tweets.")
public interface TweetComponent {

  /**
   * Gets the most recent tweets by page number.
   */
  List<Tweet> getRecentTweets(int page, int pageSize);

  ...
  
}

Identifying those components is then a matter of scanning the source or the compiled bytecode. I’ve played around with this idea on and off for a few months, using a combination of Java annotations along with annotation processors and libraries including Scannotation, Javassist and JDepend. The Reflections library on Google Code makes this easy to do, and now I have simple Java program that looks for my component annotation on classes in the classpath and automatically adds those to the model. As for the dependencies between components, again this is fairly straightforward to do with Reflections. I have a bunch of other annotations too, for example to represent dependencies between a component and a container or software system, but the principle is still the same — the architecturally significant elements and their dependencies can mostly be embedded in the code.

Creating some views

The model itself is useful, but ideally I want to look at that model from different angles, much like the diagrams that I teach people to draw when they attend my sketching workshop. After a little thought about what this means and what each view is constrained to show, I created a simple domain model to represent the context, container and component views…

Model model = ...
SoftwareSystem techTribes = model.getSoftwareSystemWithName("techtribes.je");
Container contentUpdater = techTribes.getContainerWithName("Content Updater");
 
// context view
ContextView contextView = model.createContextView(techTribes);
contextView.addAllSoftwareSystems();
contextView.addAllPeople();
 
// container view
ContainerView containerView = model.createContainerView(techTribes);
containerView.addAllSoftwareSystems();
containerView.addAllPeople();
containerView.addAllContainers();
 
// component view for the content updater container
ComponentView componentView = model.createComponentView(techTribes, contentUpdater);
componentView.addAllSoftwareSystems();
componentView.addAllContainers();
componentView.addAllComponents();
// let's exclude the logging component as it's used by everything
componentView.remove(contentUpdater.getComponentWithName("LoggingComponent"));
componentView.removeElementsWithNoRelationships();

Again, this is all in code so it’s quick to create, versionable and very customisable.

Exporting the model

Now that I have a model of my software system and a number of views that I’d like to see, I could do with drawing some pictures. I could create a diagramming tool in Java that reads the model directly, but perhaps a better approach is to serialize the object model out to an external format so that other tools can use it. And that’s what I did, courtesy of the Jackson library. The resulting JSON file is over 600 lines long (you can see it here), but don’t forget most of this has been generated automatically by Java code scanning for components and their dependencies.

Visualising the views

The last question is how to visualise the information contained in the model and there are a number of ways to do this. I’d really like somebody to build a Google Maps or Prezi-style diagramming tool where you can pinch-zoom in and out to see different views of the model, but my UI skills leave something to be desired in that area. For the meantime, I’ve thrown together a simple diagramming tool using HTML 5, CSS and JavaScript that takes a JSON string and visualises the views contained within it. My vision here is to create a lightweight model visualisation tool rather than a Visio clone where you have to draw everything yourself. I’ve deployed this app on Pivotal Web Services and you can try it for yourself. You’ll have to drag the boxes around to lay out the elements and it’s not very pretty, but the concept works. The screenshot that follows shows the techtribes.je context diagram.

Скриншот простой контекстной диаграммы

Thoughts?

All of the C4 model Java code is open source and sitting on GitHub. This is only a few hours of work so far and there are no tests, so think of this as a prototype more than anything else at the moment. I really like the simplicity of capturing a software architecture model in code, and using an architecturally-evident coding style allows you to create large chunks of that model automatically. This also opens up the door to some other opportunities such automated build plugins, lightweight documentation tooling, etc. Caveats apply with the applicability of this to all software systems, but I’m excited at the possibilities. Thoughts?