Tracabilité complète de la création des entitées / segmentation des données #3

Closed
opened 2025-11-03 20:54:53 +01:00 by ronan.quintin · 0 comments

Besoin

Il est nécessaire de mettre en place une tracabilité complète des actions. Notamment on veut savoir :

Qui a créé ou modifié les

  • Locataires
  • Bailleurs
  • Biens immobiliers
  • Baux
  • Echeances

Pour cela on va rajouter des champs dans certaines entitées et utiliser spring data pour valoriser ces champs via les annotations :

  • @CreatedDate
  • @LastModifiedDate
  • @CreatedBy

On va également créer une table audit_log pour tracer toutes les actions.

Pour cela on va introduire la notion d”Actor, qui peut se spécialiser en "User", "System" ou "ThirdPartyApp" pour savoir qui sont sont les acteurs qui font des créations modifications.

  • Les "User" sont les users humains introduit via l'évolution "sécurisation keycloak"
  • "System" represente les modifications faites par des batchs (génération automatiques d'échéances de loyer par exemple)
  • "ThirdPartyApp" : representera des applications tierces (notion qui n'existe pas encore) dans le cas où l'on voudrait ouvrir Keryloo à d'autres applications.

Voici les grands principes à retenir :

  • Audit Spring Data → gère qui et quand pour la dernière création/modification.
  • AuditLog → conserve un journal complet des actions (utile pour investigation ou conformité).
  • Actor → entité pivot qui unifie les “auteurs” (humains et applications).

Implémentation

La structure Actor/user / Third Party app est la suivante :

                +------------------------+
                |      ThirdPartyApplication |
                +------------------------+
                          ^
                          | 1
                          | 
                          | 0..1
+-----------+     +-------------+     +-------------+
| HumanUser |     |   Actor     |     |   AuditLog  |
+-----------+     +-------------+     +-------------+
| id        |     | id          |     | id          |
| keycloakId|     | type        |     | entityType  |
| name      |     | displayName |     | entityId    |
| ...       |     |             |     | action      |
              --> | humanUser   |     | timestamp   |
              --> | thirdParty  |     | changes     |
                 0..1   0..1    |     | actor (FK)  |
                                +-------------+
                                       ^
                                       |
                                       |
                              +-----------------+
                              | Customer, etc.  |
                              +-----------------+
                              | createdBy (FK)  |
                              | updatedBy (FK)  |
                              | createdAt       |
                              | updatedAt       |
                              +-----------------+

user

Le user est une entitée qui doit a minima ressembler à ceci :

@Entity
public class HumanUser {

    @Id @GeneratedValue
    private Long id;

    private String login;
    private String email;
    private String fullName;
}

Ajouter repository / service / model / controller pour pouvoir afficher la liste des utilisateurs humains et créer.

Acteurs

Création d’entité Actor de ce type :

@Entity
public class Actor {

    @Id @GeneratedValue
    private Long id;

    @Enumerated(EnumType.STRING)
    private ActorType type; // HUMAN or APPLICATION or SYSTEM

    @ManyToOne(fetch = FetchType.LAZY)
    private HumanUser humanUser;

    @ManyToOne(fetch = FetchType.LAZY)
    private ThirdPartyApplication thirdPartyApplication;

    private String displayName;
}

L’actor type :

public enum ActorType {
    HUMAN,
    APPLICATION,
    SYSTEM
}

Création d’un service ActorService qui permet de savoir quel est la personne connectée. Il s’agit soit d’un ThirdPartyApplication soit d’un utilisateur authentifié via Keycloak.

Audit Spring Data

Création de l’audit log

@Entity
public class AuditLog {

    @Id @GeneratedValue
    private Long id;

    private String entityType;
    private Long entityId;

    @Enumerated(EnumType.STRING)
    private AuditAction action; // CREATE, UPDATE, DELETE

    @ManyToOne(fetch = FetchType.LAZY)
    private Actor actor;

    private Instant timestamp;

    @Column(length = 4000)
    private String changes; // JSON des champs ou payload
}

Utilise l’audit Spring Data (@CreatedBy, AuditorAware) pour injecter automatiquement l’acteur dans les entités.

Pour cela on peut créer ceci :

@Component
@RequiredArgsConstructor
public class ActorAuditorAware implements AuditorAware<Actor> {

    private final ActorRepository actorRepository;
    private final HumanUserService humanUserService;
    private final ThirdPartyApplicationService thirdPartyService;

    @Override
    public Optional<Actor> getCurrentAuditor() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null) return Optional.empty();

        if (auth.getPrincipal() instanceof KeycloakPrincipal<?> keycloak) {
            String keycloakId = keycloak.getName();
            HumanUser user = humanUserService.findOrCreateByKeycloakId(keycloakId);
            return Optional.of(actorRepository.findOrCreateForHuman(user));
        }

        if (auth.getPrincipal() instanceof ClientPrincipal client) {
            String clientId = client.getClientId();
            ThirdPartyApplication app = thirdPartyService.findByClientId(clientId);
            return Optional.of(actorRepository.findOrCreateForApp(app));
        }

        return Optional.empty();
    }
}

Audit log listener

@Component
@RequiredArgsConstructor
public class AuditLogListener {

    private final AuditLogRepository auditLogRepository;
    private final ActorAuditorAware auditorAware;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @PostPersist
    public void afterCreate(Object entity) {
        saveLog(entity, AuditAction.CREATE);
    }

    @PostUpdate
    public void afterUpdate(Object entity) {
        saveLog(entity, AuditAction.UPDATE);
    }

    @PostRemove
    public void afterDelete(Object entity) {
        saveLog(entity, AuditAction.DELETE);
    }

    private void saveLog(Object entity, AuditAction action) {
        Actor actor = auditorAware.getCurrentAuditor().orElse(null);

        AuditLog log = new AuditLog();
        log.setEntityType(entity.getClass().getSimpleName());
        log.setEntityId(extractId(entity));
        log.setAction(action);
        log.setActor(actor);
        log.setTimestamp(Instant.now());
        log.setChanges(toJson(entity));

        auditLogRepository.save(log);
    }

    private Long extractId(Object entity) {
        try {
            Field idField = entity.getClass().getDeclaredField("id");
            idField.setAccessible(true);
            return (Long) idField.get(entity);
        } catch (Exception e) {
            return null;
        }
    }

    private String toJson(Object entity) {
        try {
            return objectMapper.writeValueAsString(entity);
        } catch (Exception e) {
            return "{}";
        }
    }
}

Les entitées concernées sont :

  • Locataires
  • Bailleurs
  • Biens immobiliers
  • Baux
  • Echeances

Audit log

Créer un ecran "audit log" qui permet d'afficher dans une datable l'ensemble des creations et modifications effectuées

# Besoin Il est nécessaire de mettre en place une tracabilité complète des actions. Notamment on veut savoir : Qui a créé ou modifié les - Locataires - Bailleurs - Biens immobiliers - Baux - Echeances Pour cela on va rajouter des champs dans certaines entitées et utiliser spring data pour valoriser ces champs via les annotations : - @CreatedDate - @LastModifiedDate - @CreatedBy On va également créer une table audit_log pour tracer toutes les actions. Pour cela on va introduire la notion d”Actor, qui peut se spécialiser en "User", "System" ou "ThirdPartyApp" pour savoir qui sont sont les acteurs qui font des créations modifications. - Les "User" sont les users humains introduit via l'évolution "sécurisation keycloak" - "System" represente les modifications faites par des batchs (génération automatiques d'échéances de loyer par exemple) - "ThirdPartyApp" : representera des applications tierces (notion qui n'existe pas encore) dans le cas où l'on voudrait ouvrir Keryloo à d'autres applications. Voici les grands principes à retenir : - Audit Spring Data → gère qui et quand pour la dernière création/modification. - AuditLog → conserve un journal complet des actions (utile pour investigation ou conformité). - Actor → entité pivot qui unifie les “auteurs” (humains et applications). # Implémentation La structure Actor/user / Third Party app est la suivante : ``` +------------------------+ | ThirdPartyApplication | +------------------------+ ^ | 1 | | 0..1 +-----------+ +-------------+ +-------------+ | HumanUser | | Actor | | AuditLog | +-----------+ +-------------+ +-------------+ | id | | id | | id | | keycloakId| | type | | entityType | | name | | displayName | | entityId | | ... | | | | action | --> | humanUser | | timestamp | --> | thirdParty | | changes | 0..1 0..1 | | actor (FK) | +-------------+ ^ | | +-----------------+ | Customer, etc. | +-----------------+ | createdBy (FK) | | updatedBy (FK) | | createdAt | | updatedAt | +-----------------+ ``` ## user Le user est une entitée qui doit a minima ressembler à ceci : ```java @Entity public class HumanUser { @Id @GeneratedValue private Long id; private String login; private String email; private String fullName; } ``` Ajouter repository / service / model / controller pour pouvoir afficher la liste des utilisateurs humains et créer. ## Acteurs Création d’entité Actor de ce type : ```java @Entity public class Actor { @Id @GeneratedValue private Long id; @Enumerated(EnumType.STRING) private ActorType type; // HUMAN or APPLICATION or SYSTEM @ManyToOne(fetch = FetchType.LAZY) private HumanUser humanUser; @ManyToOne(fetch = FetchType.LAZY) private ThirdPartyApplication thirdPartyApplication; private String displayName; } ``` L’actor type : ```java public enum ActorType { HUMAN, APPLICATION, SYSTEM } ``` Création d’un service ActorService qui permet de savoir quel est la personne connectée. Il s’agit soit d’un ThirdPartyApplication soit d’un utilisateur authentifié via Keycloak. ## Audit Spring Data Création de l’audit log ```java @Entity public class AuditLog { @Id @GeneratedValue private Long id; private String entityType; private Long entityId; @Enumerated(EnumType.STRING) private AuditAction action; // CREATE, UPDATE, DELETE @ManyToOne(fetch = FetchType.LAZY) private Actor actor; private Instant timestamp; @Column(length = 4000) private String changes; // JSON des champs ou payload } ``` Utilise l’audit Spring Data (@CreatedBy, AuditorAware) pour injecter automatiquement l’acteur dans les entités. Pour cela on peut créer ceci : ```java @Component @RequiredArgsConstructor public class ActorAuditorAware implements AuditorAware<Actor> { private final ActorRepository actorRepository; private final HumanUserService humanUserService; private final ThirdPartyApplicationService thirdPartyService; @Override public Optional<Actor> getCurrentAuditor() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth == null) return Optional.empty(); if (auth.getPrincipal() instanceof KeycloakPrincipal<?> keycloak) { String keycloakId = keycloak.getName(); HumanUser user = humanUserService.findOrCreateByKeycloakId(keycloakId); return Optional.of(actorRepository.findOrCreateForHuman(user)); } if (auth.getPrincipal() instanceof ClientPrincipal client) { String clientId = client.getClientId(); ThirdPartyApplication app = thirdPartyService.findByClientId(clientId); return Optional.of(actorRepository.findOrCreateForApp(app)); } return Optional.empty(); } } ``` ## Audit log listener ```java @Component @RequiredArgsConstructor public class AuditLogListener { private final AuditLogRepository auditLogRepository; private final ActorAuditorAware auditorAware; private final ObjectMapper objectMapper = new ObjectMapper(); @PostPersist public void afterCreate(Object entity) { saveLog(entity, AuditAction.CREATE); } @PostUpdate public void afterUpdate(Object entity) { saveLog(entity, AuditAction.UPDATE); } @PostRemove public void afterDelete(Object entity) { saveLog(entity, AuditAction.DELETE); } private void saveLog(Object entity, AuditAction action) { Actor actor = auditorAware.getCurrentAuditor().orElse(null); AuditLog log = new AuditLog(); log.setEntityType(entity.getClass().getSimpleName()); log.setEntityId(extractId(entity)); log.setAction(action); log.setActor(actor); log.setTimestamp(Instant.now()); log.setChanges(toJson(entity)); auditLogRepository.save(log); } private Long extractId(Object entity) { try { Field idField = entity.getClass().getDeclaredField("id"); idField.setAccessible(true); return (Long) idField.get(entity); } catch (Exception e) { return null; } } private String toJson(Object entity) { try { return objectMapper.writeValueAsString(entity); } catch (Exception e) { return "{}"; } } } ``` Les entitées concernées sont : - Locataires - Bailleurs - Biens immobiliers - Baux - Echeances # Audit log Créer un ecran "audit log" qui permet d'afficher dans une datable l'ensemble des creations et modifications effectuées
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: ronan.quintin/Keryloo#3
No description provided.