Skip to content

salesforce-misc/Vador

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

🦾 Vador 🦾

A piece-of-code is a costly solution to solve a simple problem

— Lord Vador

Vador is a modern Validation framework, from an API-first organization Salesforce, designed to ease and ace REST API validation.


Artifacts

Maven

<dependency>
  <groupId>com.salesforce.vador</groupId>
  <artifactId>vador</artifactId>
  <version>1.1.0</version>
</dependency>

<dependency>
  <groupId>com.salesforce.vador</groupId>
  <artifactId>vador-matchers</artifactId>
  <version>1.1.0</version>
</dependency>

Bazel

"com.salesforce.vador:vador"

Gradle Kts

implementation("com.salesforce.vador:vador:1.1.0")
implementation("com.salesforce.vador:vador-matchers:1.1.0")

Why Vador?

Why a framework for Validations? 🤷🏻♂️

Validations are a problem almost as old as programming itself, but why a new library or Framework for Validations? Validations are predominantly done with if-else-try-catch pyramids, similar to this. A domain may have many validations across its batch & non-batch services. Having validations as loose functions and using exceptions to halt the validation flow for the above requirements, can create a mess of function calls and execution flow.

function-call-mess

This approach can spike the Cyclomatic Complexity and Cognitive Complexity metrics and render a code-base which is challenging to test, extend and maintain.

USP for Vador

If you are convinced that you need a framework for validations, why use Vador?

Developed at an API-first company Salesforce, Vador is a modern Validation framework, designed to ease and ace REST API validation.

But it’s not limited to REST APIs. Vador is an independent POJO/Bean validation framework, not tied to any consumer implementation details. Its implementation is generic and can cater to anyone looking for a declarative way to validate their Beans/POJOs.

It has a unique approach to decoupling What-to-do from How-to-do. The framework asks your validation layer to be broken into 3 decoupled parts:

  • ✌🏼Validatable (What-to-do) - The Data-Structure/Bean to be Validated

  • 🧶Configuration (What-to-do) - Declare your validators/specifications/rules/constraints through a DSL (builder).

  • ⚙️Execution Strategy (How-to-do) - Call the Vador’s API as per the execution strategy (Fail-Fast or Error-Accumulation)

This decoupling comes with a lot of flexibility. Some unique selling points about Vador:

  • As Config resides outside Validatable (Bean/Data Structure/POJO), unlike annotation-based frameworks, you can declare config for classes that are not part of your code-base/module.

  • You can share Config for a Validatable across different Execution strategies.

  • Vador supports validating nested data-structures.

  • Config promotes Low-Code validations and comes with plug-and-play validators out-of-the-box that are suitable for REST API validation use-cases.

  • Failure type FailureT is generic, and Consumer has the flexibility to use a failure type of their own.

  • Vador comes with Execution Strategy algorithms out-of-the-box, even for complex nested data-structures like batch-of-batch (which might need a Tree-Traversal algorithm).

Read about more such 🍫Perks

Demo pls!

TL;DR Show me the code

💡
Refer to the Unit-tests in this repo, there is a test for every feature of Vador.
@Test
void failFastWithFirstFailureWithValidator() {
  // tag::withValidators[]
  final List<Validator<Bean, ValidationFailure>> validatorChain =
      List.of(validator(1, 9), validator(3, 7), validator(5, 5));
  final var validationConfig =
      ValidationConfig.<Bean, ValidationFailure>toValidate()
          .withValidators(Tuple.of(validatorChain, NONE))
          .prepare();
  // end::withValidators[]
  final var result = Vador.validateAndFailFast(new Bean(0), validationConfig);
  assertThat(result).contains(OUT_OF_BOUND);
}

/**
 * You can pass any number of arguments (like lowerLimit, upperLimit),
 * to write your validator closure
 */
Validator<Bean, ValidationFailure> validator(int lowerLimit, int upperLimit) {
  return bean -> bean.value >= lowerLimit && bean.value <= upperLimit ? NONE :  OUT_OF_BOUND;
}

Talks

Season of Innovation, 2021, Salesforce vav poster

💡
Watch this Tech-talk as a prerequisite to understanding the problem Vador solves and its design philosophy. It explains why if-else-try-catch is easy to start but difficult to manage and how Vador takes all that complexity away:

All Things Open, 2020, Raleigh, USA fcwfp play poster

👓A glance at the API

We’re Zealous about keeping the dev experience simple and the patterns uniform.

So, all you need is a simple API call:

API

⚙️Execution Strategy

  • Execution Strategy is how you want to Orchestrate your Validations against the Data-structure.

  • Orchestration complexity can be directly proportional to the Data structure complexity. For example, a batch-of-batch data structure might need to a Tree-Traversal algorithm (Ref: BatchOfBatch1ValidationConfig)

  • Vador provides all these Execution Strategies out-of-the-box for non-batch, batch and even batch-of-batch data structures:

Vador (For Non-Batch)

Execution Strategy Result type

validateAndFailFast

Optional<FailureT>

validateAndAccumulateErrors

List<FailureT>

VadorBatch

Execution Strategy Result type

validateAndFailFastForEach

List<Either<FailureT, ValidatableT>>

validateAndFailFastForEach (with Pair for Failure)

List<Either<Tuple2<PairT, FailureT>, ValidatableT>>

validateAndFailFastForAny

Optional<FailureT>

validateAndFailFastForAny (with Pair for Failure)

Optional<Tuple2<PairT, FailureT>>

💣 What if there is an Exception during execution 💥?

All these API methods accept an optional parameter called throwableMapper: (Throwable) → FailureT, which needs to be implemented and supplied by the consumer. If any of consumer’s validations throws a checked or unchecked exception, it shall be mapped into a FailureT using this function.

💡
You can place a logger or a debug point (during development) in this method you supply, to capture and analyze the exception info like stacktrace, cause etc. We have plans to add Logger support in the future too.

🍫 Perks of Config-based Validation

Low-Learning Curve

Use of a same Config pattern throughout, with self-explaining DSL methods to drive your development. This keeps the scope and slope of your learning curve required, low.

Readability

We don’t need analogies to stress how important readability is and how Config is more readable than code with nested if/else/for.

Maintainability

Strips out a lot of the code/logic to maintain.

Reduce Complexity

  • No branching ⇒ No Cyclomatic complexity/Cognitive complexity.

  • It abstracts away all the implementation complexity.

  • Saves a lot of dev hours while writing and 10X more while reading.

  • Eliminates the need to spike on your validation strategy/design.

💡
An 8-pointer Story for Free 🤑

Testability

It improves testability in 3 ways:

  • It forces you to write your validators as testable lambdas with a single responsibility.

  • It abstracts away all the well-tested execution logic, so you don’t need to worry about testing it.

  • Think of writing config as fill in the blanks for well-tested algorithm templates, so you don’t need to write any code, which implies no need to write any tests.

ℹ️
You can always test your config (to double-check if the right values are provided), but no need to re-test the already well-tested implementation.

Read about no-tests argument here. The same argument applies to config as well.

Flexibility

  • It’s simple to switch orchestration strategy, For example, if the current strategy is Fail-Fast for Any. But if you want to migrate to Fail-Fast for each item (to handle partial failures) or if you have another route where you need to accumulate all errors, that’s as simple as calling a different API method without changing anything else.

  • If you wish to skip some validations or add new validations depending on the route, you can have different configs instances for different routes.

Extensibility

  • Config can easily be modified or extended if your Bean’s data-structure changes, with new fields being added or removed.

  • Config can easily catch up, even when your service migrates from non-batch to batch mode.

Re-usability/Sharing

Config is mapped to a data structure. This means, if the validation requirements are the same, you can reuse the config everywhere the data-structure is used, say with a different API execution strategy. Even if the data-structure (member) is nested inside another bean (container), the container bean can reuse the member validation config and all it’s validations without rewriting.

Applications

It is predominantly used in combination with REST services, to validate the unmarshalled POJO from the REST request/response JSON.

It can also be used for SObject Validation hooks or even FTests. This is generic and can be used wherever you find a requirement to run a bunch of validations or rules on a POJO. It’s not tied to any domain or framework. It’s not even tied to Backend services; you can use Vador even in Android apps.

☄️Impact

Vador is being used in Production within Salesforce by multiple teams

This idea was presented as a Tech-talk at many International Conferences & Meetups

That said, just like us, Vador matures day-by-day, and if any of your special use-cases don’t work, we shall love to fix them ASAP (TBD - Will publish SLA for P0, P1 etc).

ℹ️
👋🏼 If you are facing any trouble or have any feature requests, please log a GitHub issue. 👋🏼

How can I get my hands dirty?

There are so many unit tests in the repo written for various features. You can start by understanding, playing with, or even writing new unit-tests to get hands-on experience with Vador.

ℹ️
Vador isn’t for some complex validation requirements. It makes even the simple validation requirements simpler. Plus, you don’t have to go full-on with the framework. All features are modular, so you may get your feet wet by migrating a small portion of your validation layer and incrementally adopting Vador.