DSL.using(java)
   .toGoBeyond(BeanValidation)
   .at(OpenRd.day);

Welcome to the Furets!

@dubreuia – Alexandre Dubreuil

  • French canadian working in Paris since 2009
  • Software Architect at LesFurets.com


@gdigugli – Gilles Di Guglielmo

  • Designer of sweet cooked software since 1999
  • Software Architect at LesFurets.com

Introduction

LesFurets service orchestration

LesFurets service orchestration

We have 71 live insurers, on 5 products, each with business validation rules, that filter prospects based on their profile

Hierarchy of 492 legacy classes, no governance or auditability

Use case: goal

  • compliance: the rules correspond to the specification documents
  • auditability: understand a rule without looking at the code
  • governance: maintenance of the rules catalogue
  • clarity: productivity for developers

Rewrite everything with a fluent API

                
public ExclusionRule exclusionRule() {
    return DOOV.when(dateContrat().after(todayPlusDays(60)))
               .exclusionRule();
}
                
              

Meet dOOv!

Domain Object Oriented Validation

dOOv is a fluent API for typesafe domain model validation

dOOv ecosystem

Live code

                
public static boolean validateAccount(User user,
                Account account, Configuration config) {
  if (config == null) {
      return false;
  }
  if (user == null || user.getBirthDate() == null) {
      return false;
  }
  if (account == null || account.getCountry() == null ||
      account.getPhoneNumber() == null) {
      return false;
  }
  if (YEARS.between(user.getBirthDate(), LocalDate.now()) >= 18
          && account.getEmail().length() <= config.getMaxEmailSize()
          && account.getCompany() == Company.LES_FURETS
          && account.getPhoneNumber().startsWith("+33")) {
      return true;
  }
  return false;
}
                
              
                
public static SampleModel sample() {
  User user = new User();
  user.setId(1);
  user.setFirstName("Foo");
  user.setLastName("BAR");
  // ...
            
  Account account = new Account();
  account.setCompany(Company.LES_FURETS);
  account.setId(9);
  // ...
                
              
                
public class Account extends Identity {

  @SamplePath(field = SampleFieldId.LOGIN, readable = "account.login")
  private String login;     
  // ...

  @SamplePath(field = SampleFieldId.COMPANY, readable = "account.company")
  private Company company;
  // ...
                
              
                
SampleModelResourceBundle_en_US.properties

account.login = account login
account.password = account password
account.country = account country
account.company = account company
...

SampleModelResourceBundle_fr_FR.properties

account.login = l'identifiant de connection
account.country = le pays
account.company = la soci\u00e9t\u00e9
...

                
              
                
> ./gradlew -p sample build

BUILD SUCCESSFUL in 23s
10 actionable tasks: 3 executed, 7 up-to-date
                
              
                
public class RulesOpenRndayTest {
  SampleModelRule demoRule = DslSampleModel
          .when(DOOV.matchAll(
                  userBirthdate.ageAt(today()).greaterOrEquals(18),
                  accountEmail.length().lesserOrEquals(configurationMaxEmailSize),
                  accountCompany.eq(Company.LES_FURETS),
                  accountPhoneNumber.startsWith("+33")))
          .validate();
}
                
              
                
@Test
public void test_account() {
    SampleModel sample = SampleModels.sample();
              
    Result result = demoRule.executeOn(sample);
              
    Assertions.assertThat(result).isTrue();
    System.out.println(demoRule.readable());
    System.out.println(demoRule.markdown(Locale.FRANCE));
}
                
              
                
rule when match all [user birthdate age at today >= 18, //
  account email length is <= configuration max email size, //
  account company = LES_FURETS, account phone number starts with '+33'] validate
* règle
  * lorsque
    * correspond à tous
      * la date de naissance âge à la date du jour >= 18
      * l'émail a une longueur <= la taille maximum de l'émail
      * la société = LES_FURETS
      * le numéro de téléphone commence par '+33'
  * valider
                
              
                
@Test
public void test_account_failure_cause() {
    SampleModel sample = SampleModels.sample();
    sample.getAccount().setPhoneNumber("+1 12 34 56 78");
              
    Result result = demoRule.executeOn(sample);
              
    Assertions.assertThat(result).isTrue();
}
                
              
                
java.lang.AssertionError: Expected result to be true 
  (invalidated nodes: [account phone number starts with '+33'])
                
              
                
@Test
public void test_account_failure_cause() {
    SampleModel sample = SampleModels.sample();
    sample.getAccount().setPhoneNumber("+1 12 34 56 78");
              
    Result result = demoRule.executeOn(sample);
              
    Assertions.assertThat(result)
            .isFalse()
            .hasFailureCause("account phone number starts with '+33'");
}
                
              
                
@Test
public void test_account_failure_cause_2() {
    SampleModel sample = SampleModels.sample();
    sample.getAccount().setPhoneNumber("+1 12 34 56 78");
    sample.getAccount().setCompany(Company.BLABLACAR);
              
    Result result = demoRule.withShortCircuit(false).executeOn(sample);
              
    Assertions.assertThat(result)
            .isFalse()
            .hasFailureCause("match all [account company = LES_FURETS, " +
                    "account phone number starts with '+33']");
}
                
              

Visit our blog for details: https://beastie.lesfurets.com

Conclusion

dOOv 2.0.0 is out!

  • Gradle code generation support
  • Better failure causes
  • New operators for some types
  • Improved documentation (javadoc and wiki)
  • dOOm beta support

What is dOOm?

Next step is extending the DSL to create an object to object mapping framework, Domain Object Oriented Mapping (dOOm). It will feature the same AST to text and statistics functionnalities.

                
DOOV.map(userEmail().to(insurerEmail())
    .map(userFirstName(), userLastName())
      .using(StringJoiner)
      .to(insurerFullName())
    .when(userCountry().eq(FR))
      .map(userPhone().to(insurerPhone()))
                
              

Stay tuned for the next versions!

Enjoy dOOv.io

http://www.dOOv.io