Base de données
Pour pour persister des informations en base de données nous allons utiliser des librairies externes. Afin de protéger le coeur de notre application la communication avec la librairie externe se fera à travers d'un adaptateur. Nous avons choisis cette solution car :
- Si la librairie externe utilisée modifie son interface alors seule la classe Adaptateur devra être adaptée.
- Si nous décidons d'utiliser une autre librairie qui ne respecte pas les spécification JPA alors seule la classe Adaptateur devra être adaptée.
Ainsi avec cette option, le coeur restera inchangé quelques soient les choix et directive sur les librairies externes
Entités JPA
Dans notre application nous avons deux types d'entités
- Les entités du domaine
- Les entités JPA
Malgré leur forte similitude il est important deux les distinguer dans deux classes séparées. En effet, le coeur applicatif ne doit pas savoir comment les entités sont persistées (fichier, bases de données, etc ...). Ainsi, le lien entre les entités du domaine et les entités JPA sera fait par l'implémentation AccountGatewayIml
Ensuite, lorsque nous créons une entité JPA nous devons préciser :
- que c'est une entité via l'annonation
@Entity
- la table où cette entité est persisté
@Table(nom_table)
- par la suite nous vous spécifier des options sur certain attributs :
@Id
pour l'identifiant@OneToOne
lorsque nous avons une relation de dépendance entre deux tables- etc ...
@Entity
@Table(name = "account")
public class AccountJpaEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@NonNull
private Long customerId;
@NonNull
private String name;
@NonNull
private LocalDateTime createdAt;
@OneToOne(cascade = { CascadeType.ALL })
@NonNull
private BalanceJPAEntity balanceJPAEntity;
@Enumerated(EnumType.STRING)
@NonNull
private AccountTypeJPAEnum accountType;
}
@Entity
@Table(name = "balance")
public class BalanceJPAEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@NonNull
private double amount;
@ElementCollection
@CollectionTable(name = "transaction", joinColumns = @JoinColumn(name = "id"))
@Column(name = "transactions")
private List<Integer> transactionList;
@NonNull
private LocalDateTime lastUpdate;
}
public enum AccountTypeJPAEnum {
CHEQUE, EPARGNE
}
Le Repository
Il orchestre l'entity manager pour la persistance en base de données. Pour créer notre propre repository nous devons implémenter l'interface JPARepository
.
En implémentant cette interface nous n'avons pas besoin d'écrire les méthodes de base comme save
, delete
, getById
, etc ... elles ont déjà été implémentées. Cependant si nous souhaitons des méthodes plus spécifique à notre problème alors nous devons les écrires.
Par exemple, nous avons définie une méthode qui permet lister tous les comptes d'un utilisateur
public interface AccountJpaRepository extends JpaRepository<AccountJpaEntity, Long> {
List<AccountJpaEntity> findByCustomerId(Long idCustomer);
Optional<AccountJpaEntity> findByCustomerIdAndName(Long idCustomer, String name);
List<AccountJpaEntity> findByCustomerIdAndAccountType(Long idCustomer, AccountTypeJPAEnum accountType);
}
Ce qui est encore plus fort, c'est que nous n'avons même pas besoin d'implémenter cette classe. En effet, en écrivant bien le nom des méthodes find
+ By
+ something
où le something
un attribut dans nos entités JPA, le framwork est capable d'écrire automatiquement la requête SQL précise.
La Gateway
Elle traduit nos :
- entités JPA en entités domaine
- entités domaine en entités JPA
public interface AccountGateway {
void create(Long idCustomer, String accountName, AccountType accountType);
void updateName(Long accountId, String newAccountName);
void delete(Long idAccount);
Account findByAccountId(Long idAccount);
Account findByCustomerIdAndByName(Long idCustomer, String name);
Account findByCustomerIdAndAccountId(Long idCustomer, Long idAccount);
List<Account> findAllAccounts(Long idCustomer);
List<Account> findByCustomerIdAndAccountType(Long customerId, AccountType accountType);
}
La méthode create
Objectif Créer un nouveau compte bancaire à un utilisateur. Elle prend donc en paramètre l'identifiant du client, le nom du compte à créer ainsi que son type.
- Crée l'ensemble des entités JPA nécessaire en utilisant les paramètres
- Appelle la méthode
save
disponible dansAccountJPARepository
(car elle implémenteJPARepository
)
@Component
public class AccountGatewayImpl implements AccountGateway {
private final AccountJpaRepository accountJpaRepository;
@Override
public void create(Long idCustomer, String accountName, AccountType accountType) {
accountJpaRepository.save(
new AccountJpaEntity(idCustomer, accountName, LocalDateTime.now(),
new BalanceJPAEntity(0.0, LocalDateTime.now()), AccountTypeJPAEnum.valueOf(accountType.name())));
}
...
}
La méthode findAllAccounts
Objectif Récupérer tous les compte bancaires qu'un utilisateur possède
- Appeler la méthode
findByCustomerId
créée précédement dansAccountJpaRepository
- Convertir chaque AccountJPAEntity en Account. En effet, la partie métier ne traite que les entités du domaine
- Retourner la liste
@Override
public List<Account> findAllAccounts(Long idCustomer) {
List<Account> toReturn = new ArrayList<>();
for (AccountJpaEntity accountJpaEntity : accountJpaRepository.findByCustomerId(idCustomer)) {
toReturn.add(accountJpaToDomain(accountJpaEntity));
}
return toReturn;
}
private Account accountJpaToDomain(AccountJpaEntity jpaEntity) {
return Account.builder().name(jpaEntity.getName())
.accountType(AccountType.valueOf(jpaEntity.getAccountType().name()))
.balance(balanceJpaToDomain(jpaEntity.getBalanceJPAEntity())).build();
}