Подтвердить что ты не робот

Обеспечение многоуровневой архитектуры в 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. Однако теперь я больше не могу использовать зависимости от копирования для сбора всех транзитивных зависимостей, необходимых для времени выполнения.

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

4b9b3361

Ответ 1

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

<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>

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

Ответ 2

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

Например, для слоев 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 (с областью выполнения). Использование области выполнения не будет вводить зависимости времени компиляции, поэтому следует избегать повторного ввода проблемы "Использование C". Однако я не уверен, что это обеспечит полное закрытие транзитивной зависимости, которое вы ищете.

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

Ответ 3

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

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

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

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

Ответ 4

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

  • Я не видел принятия этой практики в сообществе,
  • Проект JDepend, похоже, заброшен.

Ответ 5

Лучшее известное мне решение - Structure101 software. Он позволяет вам определять правила относительно зависимостей вашего кода и проверять их прямо в среде IDE или во время сборки.

Ответ 6

В 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-rules.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 maven просто поместите macker-rules.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>

Ответ 7

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

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

Ответ 8

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

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

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

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

Ответ 9

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

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

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

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

Ответ 10

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

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

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

Ответ 11

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

Альтернативный инструмент для управления уровнем времени построения времени Macker. Для этого есть плагин maven.

Ответ 12

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

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

Класс A:

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");
    }
}

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

Слой 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");
    }
}

И так далее для каждого слоя.

Ответ 13

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

  • Создание объекта (ов) должно выполняться только в специализированном классе Factory
  • Вы должны вводить код и показывать только необходимые "интерфейсы" между слоями
  • Вы должны использовать видимость класса пакета (по умолчанию).
  • При необходимости вы должны разделить свой код на отдельные подпроекты и (при необходимости) создать отдельные баннеры для обеспечения правильного межслойного зависимость.

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

Ответ 14

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

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 описывается в серии статей BLOG.

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

Ответ 15

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