Skip to main content

Best practices

> Disclaimer: This content is for educational and reference purposes for software builders only. It does not constitute medical, legal, financial, or regulatory advice. Consult a qualified professional before making decisions based on this content.

Rationale: Consent state changes over time (given, withdrawn, re-given). A mutable boolean field loses the audit trail that Art. 7 and Art. 5(2) accountability require.

Do: Create an append-only consent_events table with columns (id, subject_id, purpose_id, event_type, timestamp_utc, ip_address, user_agent, consent_text_hash, widget_version). Derive current consent state by querying the most recent event per (subject_id, purpose_id). Index on (subject_id, purpose_id, timestamp_utc DESC).

Don't: Store a single consented: boolean field that gets overwritten. You lose evidence of when consent was given, the exact wording shown, and whether the user voluntarily withdrew.

Implement a Per-Purpose Lawful Basis Registry

Rationale: GDPR Art. 5(2) accountability requires demonstrating a lawful basis for every processing activity. A registry makes this auditable and avoids legal basis drift.

Do: Create a processing_activities registry (or maintain in RoPA) with fields (activity_id, name, lawful_basis, purpose_description, data_categories, retention_days, dpia_required, dpia_completed_at). Link every data access path in code to an activity_id. Validate at startup that every activity has a valid lawful basis.

Don't: Rely on verbal assertions ("we do this for legitimate interests") without documenting the LIA, or use the same lawful basis for all activities without reviewing which actually applies to each.

Design DSAR Fulfillment as a First-Class Workflow, Not a Manual Process

Rationale: One-month response deadline (Art. 15) cannot be met reliably through ad-hoc manual data collection across dozens of data stores. Automated workflows reduce error and enable consistent, auditable responses.

Do: Build a dsar_requests table tracking (request_id, subject_id, request_type, received_at, deadline_at, status, fulfilled_at). Create extraction scripts per data store (DB, search, analytics, logs). Wire them into an orchestrator that fires on request creation. Set up deadline alerting at 20 and 27 days. Automate identity verification before running extraction.

Don't: Rely on developers manually querying data stores when requests arrive. A missed DSAR deadline is a reportable compliance gap.

Encrypt PII at Rest at Column Level for Sensitive Fields

Rationale: Full-disk encryption protects against physical theft but not against privileged insiders or database credential compromise. Column-level encryption adds a layer ensuring that even a DB-level breach exposes only ciphertext for the most sensitive fields.

Do: Use envelope encryption (a data key per row, encrypted by a master key in a KMS) for fields like email, phone, national_id, health_data. Use authenticated encryption (AES-256-GCM) to detect tampering. Rotate master keys annually; re-encrypt data keys on rotation without re-encrypting all data.

Don't: Store plaintext email addresses in all tables "for convenience". The DBA and any analyst with SELECT permission should not be able to read raw national IDs.

Rationale: Storage limitation (Art. 5(1)(e)) requires deletion of personal data when retention periods expire. Without automation, data accumulates indefinitely and creates liability.

Do: Define a retention_policies table keyed to data categories with retention_days and disposal_action. Run a scheduled job that identifies expired records, checks a legal_holds table for active holds, and performs deletion or anonymisation for records with no holds. Log every deletion event. Verify that deletion cascades to replicas and that backup restoration procedures don't restore deleted data without a separate review.

Don't: Manually delete data "periodically" without a schedule, without legal hold checks, and without logging. Undocumented deletion looks like evidence tampering in an audit.

Rationale: Consent collected at the web layer is meaningless if the analytics pipeline, email delivery system, and recommendation engine don't receive and honour withdrawal signals promptly.

Do: Publish ConsentUpdatedEvent{subjectId, purposeId, given, timestamp} to a durable event stream (Kafka topic or equivalent). All consuming services subscribe and update their local consent cache. Define a maximum propagation latency SLO (e.g., 60 seconds). Implement dead-letter handling for failed deliveries.

Don't: Let each microservice independently query the consent DB at runtime - this creates tight coupling and inconsistent consent state under latency or partition conditions.

Use Pseudonymisation in Analytics and Logging Pipelines

Rationale: Analytics and logging rarely need the real identity of users; they need a consistent identifier for session/funnel analysis. Pseudonymisation reduces risk if these secondary stores are compromised or over-retained.

Do: Replace user_id and email in analytics events with a pseudonymous analytics_token derived from a keyed HMAC (HMAC-SHA256(user_id, analytics_secret_key)). Store the mapping only in the primary DB. Analytics systems operate solely on tokens. If a subject requests erasure, delete the mapping - the analytics data becomes fully anonymous.

Don't: Log real email addresses, IP addresses in their full form, or full national IDs in application logs. Log rotation rarely keeps pace with individual erasure requests.

Document Every Data Transfer to Third Countries with a TIA

Rationale: Post-Schrems II, reliance on SCCs alone is insufficient without assessing whether the destination country's legal framework undermines the SCC protections.

Do: For every processor or sub-processor located outside the EEA/adequate countries, document a Transfer Impact Assessment covering: (a) legal framework of the destination country; (b) whether government access to data is permitted without notification; (c) whether SCCs can be honoured in practice; (d) technical supplementary measures (encryption where only EEA party holds keys). Store TIAs linked to the DPA for each third-country processor.

Don't: Sign SCCs and consider the transfer compliant without considering the Schrems II two-step test. Supervisory authorities can and do audit TIA documentation.

Implement a Structured Breach Detection and Response Runbook

Rationale: The 72-hour notification window (Art. 33) starts from the moment the controller "becomes aware" - not when the breach began. Without a detection-to-notification pipeline, awareness is delayed and the window shrinks.

Do: Define breach_severity_levels (low/medium/high/critical based on data type affected, number of subjects, reversibility). Map each level to notification obligations. Create an on-call runbook with explicit steps: detect → assess → contain → notify DPA → notify subjects (if high risk). Set up alerting on anomalous database read volumes, export operations, and authentication failures. Log dpa_notified_at, subjects_notified_at in the breach register.

Don't: Treat breach response as an ad-hoc incident. Without a documented runbook and SLOs on detection-to-awareness, the 72-hour window becomes almost impossible to meet.

Implement Granular Access Control Aligned to Data Categories

Rationale: Art. 5(1)(f) and Art. 32 require protection against unauthorised access. Access control must reflect data sensitivity - not all staff should be able to access all personal data.

Do: Implement role-based access control (RBAC) with roles scoped to data categories. Example: ROLE_CUSTOMER_SERVICE can read name, email, order_history but not health_data or financial_details. Document the access matrix in a system design document and enforce it at the ORM/query layer, not just the API layer. Log all access to sensitive data categories (who accessed what, when).

Don't: Grant broad SELECT * FROM users permissions to all internal applications. A compromised internal service or insider threat should not be able to exfiltrate all personal data categories.

Write Machine-Readable Privacy Notices Alongside Human-Readable Ones

Rationale: Data portability (Art. 20) and automated compliance tooling are increasingly important. Machine-readable privacy metadata enables automated privacy impact checks in CI/CD pipelines.

Do: Expose a /.well-known/privacy-manifest.json endpoint listing processing activities, purposes, data categories, and retention periods in structured format. This supports automated scanning, third-party audits, and future privacy tech standards (W3C DPVCG terms are a useful vocabulary).

Don't: Publish only a PDF or wall-of-text privacy policy that requires a lawyer to interpret. Machine-readable formats accelerate compliance reviews and third-party audits.

Test Erasure Completeness in Integration Tests

Rationale: Right to erasure (Art. 17) is only meaningful if deletion propagates to all data stores where the subject's data appears. Without integration tests, erasure routines silently fail to reach secondary stores.

Do: Write integration tests that: (1) seed a test subject's data across primary DB, search index, cache, and analytics store; (2) trigger an erasure request; (3) assert that the data is absent from each store after the erasure workflow completes. Include the test in CI and fail the build if any store retains the test subject's data post-erasure.

Don't: Unit-test the erasure function in isolation and assume it works correctly end-to-end. Erasure is a distributed operation; its correctness must be verified at the system level.