Other data storage

You can use the GDPR for Akka Persistence encryption utilities even if you are not using Akka Persistence or Lagom Persistence. However, first consider whether it’s easier to perform ordinary removal of the data instead of encrypting and shredding it.

Here is an example of how to use the WithDataSubjectIdSerialization utility in a JDBC data access object. The same technique can be used with Lagom’s Read-side Lagom’s Read-side, plain JPA, a NoSQL key value store, or many other data stores.

Each WithDataSubjectId part must be represented as bytes (blob) in the stored representation and you should use the WithDataSubjectIdSerialization utility to serialize each WithDataSubjectId to/from the bytes.

Scala
final case class PersonalInformation(name: String, email: String)

final case class Customer(id: Long, personal: WithDataSubjectId[PersonalInformation])

class CustomerDbAccess(system: ActorSystem, blockingExectionContext: ExecutionContext) {
  private val withDataSubjectIdSerialization = new WithDataSubjectIdSerialization(system)

  // important to use a dedicated dispatcher for the blocking JDBC calls
  private implicit val ec: ExecutionContext = blockingExectionContext
  private val updatePstmt: PreparedStatement = ??? // TODO the prepared statement for the update
  private val readPstmt: PreparedStatement = ??? // TODO the prepared statement for the read

  def update(customer: Customer): Future[Done] = {
    withDataSubjectIdSerialization.toBinaryAsync(customer.personal).map { personalBytes =>
      updatePstmt.setLong(1, customer.id)
      updatePstmt.setBlob(2, new SerialBlob(personalBytes))
      updatePstmt.execute()
      Done
    }
  }

  def read(customerId: Long): Future[Customer] = {
    def personalBytes(): Future[Array[Byte]] = Future {
      val rs = readPstmt.executeQuery()
      try {
        val blob = rs.getBlob(2)
        blob.getBytes(0, blob.length.toInt)
      } finally {
        rs.close()
      }
    }

    for {
      bytes <- personalBytes()
      personal <- withDataSubjectIdSerialization.fromBinaryAsync[PersonalInformation](bytes)
    } yield {
      Customer(customerId, personal)
    }
  }

}
Java
public static class PersonalInformation {
  public final String name;
  public final String email;

  public PersonalInformation(String name, String email) {
    this.name = name;
    this.email = email;
  }
}

public static class Customer {
  public final long id;
  public final WithDataSubjectId<PersonalInformation> personal;

  public Customer(long id, WithDataSubjectId<PersonalInformation> personal) {
    this.id = id;
    this.personal = personal;
  }
}

public static class CustomerDbAccess {
  private final ActorSystem system;
  // important to use a dedicated thread pool for the blocking JDBC calls
  private final Executor blockingExecutor;
  private final WithDataSubjectIdSerialization withDataSubjectIdSerialization;

  private PreparedStatement updatePstmt = null; // TODO the prepared statement for the update
  private PreparedStatement readPstmt = null; // TODO the prepared statement for the read

  public CustomerDbAccess(ActorSystem system, Executor blockingExecutor) {
    this.system = system;
    this.blockingExecutor = blockingExecutor;
    this.withDataSubjectIdSerialization = new WithDataSubjectIdSerialization(system);
  }

  public CompletionStage<Done> update(Customer customer) {
    return withDataSubjectIdSerialization.toBinaryAsync(customer.personal)
        .thenApplyAsync(personalBytes -> {
          try {
            updatePstmt.setLong(1, customer.id);
            updatePstmt.setBlob(2, new SerialBlob(personalBytes));
            updatePstmt.execute();
            return Done.getInstance();
          } catch (SQLException e) {
            throw new RuntimeException(e);
          }
        }, blockingExecutor);
  }

  public CompletionStage<Customer> read(long customerId) {
    CompletionStage<byte[]> personalBytes = CompletableFuture.supplyAsync(() -> {
      ResultSet rs = null;
      try {
        rs = readPstmt.executeQuery();
        Blob blob = rs.getBlob(2);
        return blob.getBytes(0, (int) blob.length());
      } catch (SQLException e) {
        throw new RuntimeException(e);
      } finally {
        if (rs != null) {
          try {
            rs.close();
          } catch (SQLException ignore) {
          }
        }
      }
    }, blockingExecutor);

    CompletionStage<Customer> customer = personalBytes.thenComposeAsync(bytes -> {
      return withDataSubjectIdSerialization.fromBinaryAsync(PersonalInformation.class, bytes)
          .thenApply(personal -> {
            return new Customer(customerId, personal);
          });
    }, blockingExecutor);

    return customer;
  }


}