Как описать API компонента в модульном монолите

Так же как модификаторы доступа позволяют менять внутренности класса, не затрагивая использующий его код, граница API позволяет менять внутренности компонента, не затрагивая другие компоненты. Например, в CQRS в API компонента входит лишь часть команд и событий, а остальные используются только внутри компонента.

В Java нет полноценного встроенного механизма, позволяющего явно выразить API компонента. Закрывать всё пакетной областью видимости не всегда возможно и не всегда удобно. Например, конструкторы классов-сервисов нужно держать открытыми, чтобы точка запуска могла создавать их экземпляры, хотя сами эти конструкторы не являются частью API компонента. Размещение всех сервисов в одном пакете с точкой запуска также не решает проблемы, поскольку частью API могут быть классы сторонних библиотек, чьи конструкторы и методы, очевидно, останутся публичными. Модульность, появившаяся в Java 9, также не решает эту проблему, поскольку работает на уровне пакетов, тогда как нам требуется возможность разделять API на уровне отдельных методов.

Для решения этой проблемы можно использовать архитектурные тесты на базе ArchUnit, однако поддерживать такие правила довольно трудоёмко.

Другим решением проблемы может быть создание собственной аннотации (например, @Public) и плагина к компилятору, который ещё на этапе компиляции проверяет, что другие компоненты обращаются только к методам, помеченным этой аннотацией. Мы предпочитаем именно этот способ, для чего используем аннотацию и плагин, предоставляемые проектом domain-model.

Наконец, можно не использовать никаких дополнительных инструментов, а опираться исключительно на формальное определение API, установленное в вашей команде.