Применение многоуровневой архитектуры в Java

учитывая программную систему, написанную на Java, состоящую из трех слоев, A - > B - > C, т. е. слой A использует слой B и B использует слой C.

Я хочу убедиться, что класс одного слоя имеет доступ только к классам одного слоя или его прямой зависимости, т. е. B должен иметь доступ к C, но не A. Также A должен иметь доступ к B, но не C.

есть ли простой способ реализовать такое ограничение? В идеале я хочу, чтобы eclipse жаловался сразу, если кто-то пытается доступ к классу неправильного слоя.

программное обеспечение в настоящее время использует maven. Поэтому я попытался поместить A, B и C в разные модули maven и правильно объявить зависимости. Это отлично работает, чтобы предотвратить доступ B к A, но не предотвращает доступ A К C.

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

есть ли хороший способ, который позволяет мне чистое разделение слоев, но также позволяет мне собирать все необходимые зависимости времени выполнения?

15 ответов


возможно, вы можете попробовать это в pom:

<dependency>
    <groupId>the.groupId</groupId>
    <artifactId>moduleB</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>the.groupId</groupId>
            <artifactId>moduleC</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>the.groupId</groupId>
    <artifactId>moduleC</artifactId>
    <version>1.0</version>
    <scope>runtime</scope>
</dependency>

может ли это помочь вам?


Хм - интересно. Я, конечно, сталкивался с этой проблемой раньше, но никогда не пытался реализовать решение. Мне интересно, можете ли вы представить интерфейсы как слой абстракции - что-то похожее на шаблон фасада, а затем объявить зависимости от этого.

например, для слоев B и C создайте новые проекты maven, которые содержат только интерфейсы в этих слоях, назовем эти проекты B' и C'. Затем вы объявите зависимости только для уровень интерфейса, а не уровень реализации.

таким образом, A будет зависеть от B' (только). B будет зависеть от B' (потому что он будет реализовывать объявленные там интерфейсы) и C'. Тогда C будет зависеть от C'. Это предотвратит проблему "A использует C", но вы не сможете получить зависимости среды выполнения.

оттуда вам нужно будет использовать теги области maven для получения зависимостей среды выполнения (http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html) - ... Это часть, которую я действительно не исследовал, но я думаю, что вы можете использовать область "выполнения" для добавления зависимостей. Таким образом, вам нужно будет добавить A зависит от B (с областью выполнения) и аналогично, B зависит от C (с областью выполнения). Использование области выполнения не будет вводить зависимости времени компиляции, поэтому следует избегать повторного введения проблемы "A использует C". Однако я не уверен, что это будет предоставьте полное транзитивное закрытие зависимостей, которое вы ищете.

Мне было бы очень интересно услышать, можете ли вы придумать рабочее решение.


вероятно, это не то решение, которое вы ищете, и я не пробовал, но, возможно, вы могли бы попробовать использовать checkstyle.

представьте, что пакеты в модуле C называются"org.проект.модульек...", пакеты в модуле B"org.проект.moduleb...." и пакеты в модуль "org.проект.модуль....".

вы можете настроить Maven-checkstyle-плагин в каждом модуле и искать незаконные имена пакетов. Т. е. в модуль a настройте как незаконный импорт пакетов, называемых org.проект.модульек. Посмотрите наhttp://checkstyle.sourceforge.net/config_imports.html (IllegalImport)

вы можете настроить Maven-checkstyle-plugin и каждый раз, когда вы компилируете проверку на незаконный импорт и делаете компиляцию неудачной.


Я предложу то, что я никогда не пробовал сам-написание модульных тестов с JDepend для проверки архитектурных зависимостей. документация JDepend дает пример этого как "тест ограничения зависимости". Два основных предостережения

  1. Я не видел никакого принятия этой практики в сообществе,
  2. проект JDepend кажется заброшенным.

лучшие решения известных мне программное обеспечение Structure101. Это позволяет определить правила о зависимостях кода и проверить их прямо в среде IDE или во время сборки.


в maven вы можете использовать Maven-macker-plugin в следующем примере:

<build>
    <plugins>
        <plugin>
            <groupId>de.andrena.tools.macker</groupId>
            <artifactId>macker-maven-plugin</artifactId>
            <version>1.0.2</version>
            <executions>
                <execution>
                    <phase>compile</phase>
                    <goals>
                        <goal>macker</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

и вот пример macker-правил.пример файла xml: (поместите его на тот же уровень, что и ваш pom.в XML)

<?xml version="1.0"?>
<macker>

    <ruleset name="Layering rules">
        <var name="base" value="org.example" />

        <pattern name="appl" class="${base}.**" />
        <pattern name="common" class="${base}.common.**" />
        <pattern name="persistence" class="${base}.persistence.**" />
        <pattern name="business" class="${base}.business.**" />
        <pattern name="web" class="${base}.web.**" />

        <!-- =============================================================== -->
        <!-- Common -->
        <!-- =============================================================== -->
        <access-rule>
            <message>zugriff auf common; von überall gestattet</message>
            <deny>
                <to pattern="common" />
                <allow>
                    <from>
                        <include pattern="appl" />
                    </from>
                </allow>
            </deny>
        </access-rule>

        <!-- =============================================================== -->
        <!-- Persistence -->
        <!-- =============================================================== -->
        <access-rule>
            <message>zugriff auf persistence; von web und business gestattet</message>
            <deny>
                <to pattern="persistence" />
                <allow>
                    <from>
                        <include pattern="persistence" />
                        <include pattern="web" />
                        <include pattern="business" />
                    </from>
                </allow>
            </deny>
        </access-rule>

        <!-- =============================================================== -->
        <!-- Business -->
        <!-- =============================================================== -->
        <access-rule>
            <message>zugriff auf business; nur von web gestattet</message>
            <deny>
                <to pattern="business" />
                <allow>
                    <from>
                        <include pattern="business" />
                        <include pattern="web" />
                    </from>
                </allow>
            </deny>
        </access-rule>

        <!-- =============================================================== -->
        <!-- Web -->
        <!-- =============================================================== -->
        <access-rule>
            <message>zugriff auf web; von nirgends gestattet</message>
            <deny>
                <to pattern="web" />
                <allow>
                    <from>
                        <include pattern="web" />
                    </from>
                </allow>
            </deny>
        </access-rule>

        <!-- =============================================================== -->
        <!-- Libraries gebunden an ein spezifisches Modul -->
        <!-- =============================================================== -->
        <access-rule>
            <message>nur in web erlaubt</message>
            <deny>
                <to>
                    <include class="javax.faces.**" />
                    <include class="javax.servlet.**" />
                    <include class="javax.ws.*" />
                    <include class="javax.enterprise.*" />
                </to>
                <allow>
                    <from pattern="web" />
                </allow>
            </deny>
        </access-rule>

        <access-rule>
            <message>nur in business und persistence erlaubt</message>
            <deny>
                <to>
                    <include class="javax.ejb.**" />
                    <include class="java.sql.**" />
                    <include class="javax.sql.**" />
                    <include class="javax.persistence.**" />
                </to>
                <allow>
                    <from>
                        <include pattern="business" />
                        <include pattern="persistence" />
                    </from>
                </allow>
            </deny>
        </access-rule>

    </ruleset>

</macker>

и в простом многомодульном проекте maven просто поместите правила macker.xml в центральном месте и указывают на каталог, где он хранится. затем вам нужно настроить плагин в вашем Родительском pom.в XML

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>de.andrena.tools.macker</groupId>
                <artifactId>macker-maven-plugin</artifactId>
                <version>1.0.2</version>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>macker</goal>
                        </goals>
                        <configuration>
                            <rulesDirectory>../</rulesDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

на вашем месте я бы сделал следующие шаги:

  • для каждого слоя создайте два модуля. Один для интерфейсов другой для реализации.
  • сделайте правильную зависимость maven, избегая транзитивных зависимостей.
  • установить sonargraph-плагин архитектора в eclipse. Это позволит вам настроить правила слоя.

Я бы извлекал интерфейсы из модуля B, т. е. у вас будут B и B-Impl

в этом случае вы получите следующие зависимости:

  • A зависит от B
  • B-Impl зависит от B и c

для сборки артефакта развертывания вы можете создать отдельный модуль без какого-либо кода, который будет зависеть от A и B-Impl


вы можете определить правила доступа для артефактов classpath в Eclipse. Правила доступа могут использоваться для отображения шаблона, например " com.образец.* "к резолюции, например "запрещено". Это приводит к предупреждению компилятора при определении импорта в ограниченное расположение.

хотя это очень хорошо работает для небольших наборов кодов, определение правил доступа может быть очень утомительным для больших проектов. Имейте в виду, что это проприетарная функция Eclipse, и поэтому правила доступа хранятся в Eclpise конкретная конфигурация проекта.

чтобы определить правила доступа, следуйте этому clickpath: Свойства проекта > Путь сборки Java > библиотеки > [ваша библиотека или модуль Maven] > правила доступа > нажмите "Изменить"

правила доступа могут быть определены глобально в меню настроек.


похоже, вы пытаетесь сделать что-то, что Maven делает из коробки.

Если модуль A зависит от B с предложением exclude C, классы C недоступны в A без явной зависимости от C. Но они существуют для B, так как B напрямую зависит от них.

затем, когда вы упаковываете свое решение, вы запускаете сборку или что-то еще на модуле R, который является родителем A, B и C, и собирает их зависимости без усилий.


вы можете достичь этого, сделав свои артефакты JAR OSGI пакеты, которые применяют такие слои. Либо вручную создавая свой JAR-манифест (также возможно через Maven), используя директивы OSGI, либо используя поддержку инструментов. Если вы используете Maven, вы можете выбрать один из множества плагинов maven для достижения этой цели. Аналогично для IDE, как Eclipse, где вы можете выбирать между различными плагинами Eclipse, как PDE или bndtools.

альтернативный инструмент для время сборки дизайн слой управления Macker. Существует также плагин maven для этого.


если вы хотите это сделать, вам нужен объект, который может быть определен только в Слой и ключ необходима Слой B. То же самое для Слой C: к нему можно получить доступ, только дав ключ (объект), который может быть создан только с Слой B.

это код, который я только что создал, который покажет вам, как реализовать эту идею с 3 Классы:

Класс:

public class A
{
    /* only A can create an instance of AKey */
    public final class AKey
    {
        private AKey() {

        }
    }


    public A() {
        B b = new B(new AKey());
        b.f();
    }
}

Класс B:

public class B
{
    /* only B can create an instance of BKey */
    public final class BKey
    {
        private BKey() {

        }
    }


    /* B wants an instance of AKey, and only A can create it */
    public B(A.AKey key) {
        if (key == null)
            throw new IllegalArgumentException();

        C c = new C(new BKey());
        c.g();
    }


    public void f() {
        System.out.println("I'm a method of B");
    }
}

Класс C:

public class C
{
    /* C wants an instance of BKey, and only B can create it */
    public C(B.BKey key) {
        if (key == null)
            throw new IllegalArgumentException();
    }


    public void g() {
        System.out.println("I'm a method of C");
    }
}

теперь, если вы хотите расширить это поведение к конкретному пласт, вы можете сделать, как показано ниже:

Слой A:

public abstract class AbstractA
{
    /* only SUBCLASSES can create an instance of AKey */
    public final class AKey
    {
        protected AKey() {

        }
    }
}

public class A extends AbstractA
{
    public A() {
        B b = new B(new AKey());
        b.f();

        BB bb = new BB(new AKey());
        bb.f();
    }
}

public class AA extends AbstractA
{
    public AA() {
        B b = new B(new AKey());
        b.f();

        BB bb = new BB(new AKey());
        bb.f();
    }
}

Слой B:

public abstract class AbstractB
{
    /* only SUBCLASSES can create an instance of BKey */
    public final class BKey
    {
        protected BKey() {

        }
    }
}

public class B extends AbstractB
{
    /* B wants an instance of AKey, and only A Layer can create it */
    public B(AbstractA.AKey key) {
        if (key == null)
            throw new IllegalArgumentException();

        C c = new C(new BKey());
        c.g();

        CC cc = new CC(new BKey());
        cc.g();
    }


    public void f() {
        System.out.println("I'm a method of B");
    }
}

public class BB extends AbstractB
{
    /* BB wants an instance of AKey, and only A Layer can create it */
    public BB(AbstractA.AKey key) {
        if (key == null)
            throw new IllegalArgumentException();

        C c = new C(new BKey());
        c.g();

        CC cc = new CC(new BKey());
        cc.g();
    }


    public void f() {
        System.out.println("I'm a method of BB");
    }
}

Слой C:

public class C
{
    /* C wants an instance of BKey, and only B Layer can create it */
    public C(B.BKey key) {
        if (key == null)
            throw new IllegalArgumentException();
    }


    public void g() {
        System.out.println("I'm a method of C");
    }
}

public class CC
{
    /* CC wants an instance of BKey, and only B Layer can create it */
    public CC(B.BKey key) {
        if (key == null)
            throw new IllegalArgumentException();
    }


    public void g() {
        System.out.println("I'm a method of CC");
    }
}

и так далее для каждой слой.


для структурирования программного обеспечения вам нужно воспользоваться лучшими практиками кодирования и шаблонами проектирования. У меня есть несколько моментов, которые определенно помогут.

  1. создание объектов должно производиться только в специализированном Заводском классе(классах)
  2. вы должны кодировать и выставлять только необходимые "интерфейсы" между слоями
  3. вы должны воспользоваться видимостью класса области пакета (по умолчанию).
  4. при необходимости вы должны разделить свой код на отдельные подпроекты и (при необходимости) создать отдельные jar(ы) для обеспечения надлежащего межслойного зависимость.

наличие хорошего дизайна системы завершит и превысит вашу цель.


вы можете описать свою архитектуру с использованием Sonargraph это новый DSL:

artifact A
{
  // Pattern matching classes belonging to A
  include "**/a/**"
  connect to B
}  
artifact B
{
  include "**/b/**"
  connect to C
}
artifact C
{
  include "**/c/**"
}

DSL описывается в серии статьи.

затем вы можете запустить Sonargraph через Maven или Gradle или аналогичный в вашей сборке и сделать сборку неудачной, когда происходят нарушения правил.


Почему бы просто не использовать разные проекты для каждого слоя? Вы помещаете их в рабочую область и управляете зависимостями сборки, как хотите.