# CleanArchitecture JPA Integration



## 687b46c0da97 (10. Dez 2019)

Hallo,
ich versuche aktuell ein Projekt aufzusetzen um Themen wie Clean Architecture, JPA & CDI näher zu behandeln.
Leider habe ich Probleme, die Datenbankverbindung zum Laufen zu bringen. Geplannt ist, mittels JBoss die Verbindung im Container managen zu lassen (Datasource etc. im Jboss zu definieren), damit ich mir über Sachen wie Connection pooling etc. keine Gedanken machen muss.

Das Projekt soll im ersten Schritt nur zwei Funktionen bieten:

Create User (Register User)
Login User
Zuersteinmal zu meinem Setup (Programmiert wird mit IntelliJ IDEA):




web-app: Das ist meine JSF Applikation welche als Frontend agieren soll. Später sollen andere Projekte hinzukommen, um andere "Presentation Layer" zu Verfügung zu stellen (Console Application, API Projekt, ...)
usecases: Beinhaltet die BusinessLogic. z.B. "LoginUser.java" (Beispiel unten). Dieses Projekt basiert auf Contracts (z.B. Interfaces für "UserRepository") und domain (Domain beeinhaltet die Entities)
domain: Beeinhaltet Entities (z.B. User.java), welche nicht an andere "Frameworks" gebunden sind
contracts: Beeinhaltet die Interfaces für z.B. UserRepository, PasswordHasher, ...
adapters: Beeinhaltet die Implementierung für Interfaces (z.B. JPAUserRepository, BCryptHasher)
Mit dieser Architektur, wären die "Presentation Layers" in der Verantwortung, die interfaces korrekt zu initialisieren. Dafür habe ich mich für Spring Boot entschieden:


```
@Configuration
@EntityScan("de.test.adapters.hibernate.entity")
public class SpringCoreConfig {
    private final PasswordHasher passwordHasher = new BCryptHasher();
    private final IdGenerator idGenerator = new GuidGenerator();

    @PersistenceContext(name = "entities")
    private EntityManager entityManager;

    @Bean
    public UserService userService(){
        return new UserService();
    }

    @Bean
    public CreateUser createUser() {
        return new CreateUser(getUserRepository(), passwordHasher, idGenerator);
    }

    @Bean
    public LoginUser loginUser() {
        return new LoginUser(getUserRepository(), passwordHasher);
    }

    private UserRepository getUserRepository(){
        return new JpaUserRepository(getEntityManager());
    }

    private EntityManager getEntityManager(){
        return entityManager;
    }
}
```

Sollte ich nun eine neue Presentation Layer einfügen z.B. Console Application, müsste diese die "Interfaces" neu initialisieren. Das ist i.O., da dort z.B. der EntityManager anderst gehandelt werden muss.

Beispiel für JpaUserRepository:

```
public class JpaUserRepository implements UserRepository {

    private EntityManager entityManager;

    public JpaUserRepository(EntityManager em){
        this.entityManager = em;
    }

    @Override
    public User create(User user) {
        entityManager.persist(UsersEntity.fromUser(user));
        return null;
    }

    @Override
    public Optional<User> findById(String id) {
        return Optional.empty();
    }

    @Override
    public Optional<User> findByEmail(String email) {
        return Optional.empty();
    }
}
```

Nun habe ich das Problem, dass der EntityManager in der Configuration nicht ordentlich initialisiert werden kann:

```
Caused by: java.lang.NullPointerException
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:580)
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:546)
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:707)
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.getResourceToInject(PersistenceAnnotationBeanPostProcessor.java:680)
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:169)
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
   at deployment.web-app-1.0-SNAPSHOT.war//org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessPropertyValues(PersistenceAnnotationBeanPostProcessor.java:354)
   ... 35 more
```

Die persistence.xml liegt innerhalb des web-app projektes:

```
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <persistence-unit name="entities" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:jboss/datasources/testDS</jta-data-source>
        <properties>
            <property name="hibernate.archive.autodetection" value="class, hbm"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="jboss.entity.manager.factory.jndi.name" value="java:/EntityManagerFactory" />
            <property name="jboss.entity.manager.jndi.name" value="java:/EntityManager" />
        </properties>
    </persistence-unit>
</persistence>
```

Meine POM.xml von der web-app:

```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>de.test</groupId>
        <artifactId>test</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>web-app</artifactId>
    <packaging>war</packaging>


    <properties>
        <com.sun.faces.version>2.2.14</com.sun.faces.version>
        <javax.el.version>3.0.0</javax.el.version>
        <maven-war-plugin.version>3.0.0</maven-war-plugin.version>
        <javax.servlet-api.version>3.1.0</javax.servlet-api.version>
        <jsf.omnifaces.version>3.4</jsf.omnifaces.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.omnifaces</groupId>
            <artifactId>omnifaces</artifactId>
            <version>${jsf.omnifaces.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>${logback.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j.version}</version>
        </dependency>
        <!-- JSF -->
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-api</artifactId>
            <version>${com.sun.faces.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.faces</groupId>
            <artifactId>jsf-impl</artifactId>
            <version>${com.sun.faces.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>${javax.el.version}</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
            <version>${javax.servlet-api.version}</version>
        </dependency>
        <dependency>
            <groupId>de.test</groupId>
            <artifactId>usecases</artifactId>
        </dependency>
        <dependency>
            <groupId>de.test</groupId>
            <artifactId>domain</artifactId>
        </dependency>
        <dependency>
            <groupId>de.test</groupId>
            <artifactId>adapters</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.2.1.RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>${maven-war-plugin.version}</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
```

Ich gehe davon aus, dass ich einen Fehler gemacht habe bzgl JPA. Leider komme ich auch mit meiner Recherche nicht weiter, weswegen ich nun hier um Hilfe bitte.


----------



## Ullenboom (10. Dez 2019)

Mir ist nicht ganz klar, warum du Spring Boot und JBoss mischt. Lass doch JBoss gleich weg und mache alles mit Spring Boot, das ist schlanker. Es gibt Spring Data JPA, damit kannst du entweder den EntityManger nutzen oder gleich Repositories, was noch ein bisschen komfortabler ist. Und JoinFaces gibt es auch für Spring Boot als Starter, das ist sehr einfach, nutzt ich auch in einem Projekt. Dann ist der Flow ganz einfach: Die JSF-Managed Beans bekommen die Services injiziert, die wiederum die Repositories.


----------



## 687b46c0da97 (10. Dez 2019)

Tatsächlich vermutlich Unwissenheit. Prinzipiell möchte ich einen ApplicationServer als Basis nutzen, um einfach hier von den Vorteilen zu profitieren, wobei JBoss hier eher durch Zufall ggewählt ist (Später ggf. Websphere).
Wie bereits gesagt, habe ich ein bisschen Probleme bzgl. der CDI & JPA. Spring nutze ich, da ich vermutet habe es dadurch einfacher zu haben, vermutlich geht es auch ohne? Wenn ich also nun auf den ApplicationServer nicht verzichten möchte, was würdest du vorschlagen?

Die Services innerhalb der webapplikation, haben keinen Zugriff auf die Repositories, da diese nur über die use cases angesprochen werden sollen. Diese "Services" innerhalb der webapplikation sind nicht zwingend notwendig, sind hier nur eleganter um die einzelnen use cases zu bündeln.

Hier noch ein Beispiel eines use-cases (Hatte ich oben vergessen):


```
public final class CreateUser {
    private final UserRepository repository;
    private final PasswordHasher passwordHasher;
    private final IdGenerator idGenerator;

    public CreateUser(final UserRepository repository, final PasswordHasher passwordHasher, final IdGenerator idGenerator) {
        this.repository = repository;
        this.passwordHasher = passwordHasher;
        this.idGenerator = idGenerator;
    }

    public User create(final User user) {
        //TODO: Validate user object
        if (repository.findByEmail(user.getEmail()).isPresent()) {
            //throw new UserAlreadyExistsException(user.getEmail());
        }
        var userToSave = User.builder()
                .id(idGenerator.generate())
                .email(user.getEmail())
                .password(passwordHasher.hash(user.getPassword()))
                .lastName(user.getLastName())
                .firstName(user.getFirstName())
                .build();
        return repository.create(userToSave);
    }
}
```


----------

