DEV Community

Sebastien Lato
Sebastien Lato

Posted on

SwiftUI Data Integrity & Validation Pipelines (Trust Nothing, Break Nothing)

Most production bugs are not crashes.

They are bad data:

  • impossible states
  • partial writes
  • silent corruption
  • invalid assumptions
  • “this should never happen”

And once bad data enters your system, it spreads everywhere.

This post shows how to design data integrity & validation pipelines in SwiftUI that:

  • prevent corruption
  • enforce correctness
  • surface issues early
  • stay testable
  • scale with complexity

🧠 The Core Principle

Data must be validated at every boundary.

If data crosses a boundary without validation, corruption is inevitable.


🧱 1. Identify Trust Boundaries

Every app has trust boundaries:

  • network → app
  • persistence → memory
  • user input → model
  • feature → feature
  • version → version

Never assume data is valid across boundaries.


🧬 2. Validation Is a Pipeline, Not a Check

Bad:

if value.isValid { save(value) }
Enter fullscreen mode Exit fullscreen mode

Correct:

RawInput
  Sanitization
    Validation
      Normalization
        Domain Model
Enter fullscreen mode Exit fullscreen mode

Each step has a clear responsibility.


📦 3. Separate Raw Models from Domain Models

struct UserDTO {
    let email: String?
    let age: Int?
}
Enter fullscreen mode Exit fullscreen mode
struct User {
    let email: Email
    let age: Age
}
Enter fullscreen mode Exit fullscreen mode

Domain models cannot represent invalid states.


🔐 4. Typed Validation Objects

struct Email {
    let value: String

    init?(_ raw: String) {
        guard raw.contains("@") else { return nil }
        self.value = raw
    }
}
Enter fullscreen mode Exit fullscreen mode

If it exists, it’s valid — no rechecking required.


🧭 5. Central Validation Layer

Never validate in views.

protocol Validator {
    func validate(_ input: RawInput) throws -> DomainModel
}
Enter fullscreen mode Exit fullscreen mode

This allows:

  • reuse
  • testing
  • consistent rules
  • versioned validation

🧪 6. Validation Errors Are First-Class

enum ValidationError: Error {
    case missingField(String)
    case invalidFormat(String)
    case outOfRange(String)
}
Enter fullscreen mode Exit fullscreen mode

Never use generic errors.

Validation errors:

  • inform UX
  • inform analytics
  • inform monitoring

🧠 7. Persistence Validation

Before saving:

func save(_ model: DomainModel) throws {
    try invariantCheck(model)
    persist(model)
}
Enter fullscreen mode Exit fullscreen mode

Never trust in-memory state blindly.


🔁 8. Versioned Validation

Validation rules change over time.

enum ValidationVersion {
    case v1
    case v2
}
Enter fullscreen mode Exit fullscreen mode

This allows:

  • backward compatibility
  • safe migrations
  • gradual tightening of rules

⚠️ 9. Detect & Surface Corruption

When corruption is detected:

  • log it
  • capture context
  • isolate the data
  • fail safely

Silent corruption is worse than crashes.


❌ 10. Common Data Integrity Anti-Patterns

Avoid:

  • optional-everything models
  • validation in views
  • trusting backend blindly
  • skipping validation for “internal” data
  • mixing validation with UI
  • ignoring edge cases

Bad data compounds.


🧠 Mental Model

Think:

Input
  Validate
    Normalize
      Persist
        Trust
Enter fullscreen mode Exit fullscreen mode

Not:

“It looks fine”


🚀 Final Thoughts

Data integrity architecture gives you:

  • fewer bugs
  • safer migrations
  • predictable behavior
  • easier debugging
  • long-term stability

Security protects access.
Integrity protects truth.

Top comments (0)