Download - Specifications pattern - Teds Tool Time
Ted’s Tool TimeTed VinkeFirst8
Specifications Pattern
August 2016
“Separate the statement of how to match a candidate, from the candidate object that it is matched against”
-- Martin Fowlerhttp://martinfowler.com/apsupp/spec.pdf
Using specifications to describe the selection criteria for portfolios in contracts.
Using specifications to describe the selection criteria for portfolios in contracts.
Using specifications to constrain which containers can be used for transporting a cargo
Using a specification as an input to a route selector. This decouples the route selector from the
shipment.
Advantages
Treating the specification as a separate object has a number of advantages
Lets you decouple the design of requirements, fulfillment, and validation
Allows you to make your systemdefinitions more clear and declarative
3 simple requirements
1. Animal should be
female
2. Animal should not have
been tested before
(Herdbook API)
3. No existing genomic
tests (Breeding API)
Is a genomic test allowed?version 1
(Warning: Groovy ahead!)
Render only animals for which genomic test is allowed
Need:
HousingService housingService
Iterate and check:
Collection<Animal> animals = housingService.retrieveAnimals()
animals.each { animal ->
boolean matches = isGenomicTestAllowed(animal)
Animal should be female
Need:
class Animal {
AnimalId id
String name
String gender
}
Check:
gender.toLowerCase() == ‘female’
Animal should not have been tested before
Need:
HerdbookRepository herdbookRepository
Check:
AnimalHeredityResource heredity =
herdbookRepository.retrieveHeredity(animal.id)
heredity != null
No existing genomic tests
Need:
BreedingRepository breedingRepository
Check:
Collection<GenomicTestResource> testResources = breedingRepository.retrieveTests()
GenomicTestResource existingTest = testResources.find { test ->
test.animalId == animal.id
}
Putting them together
HousingService housingService
HerdbookBackendRepository herdbookBackendRepository
BreedingRepository breedingRepository
boolean isGenomicTestAllowed(Animal animal) {
boolean genderMatch = gender.toLowerCase() == ‘female’
AnimalHeredityResource heredity = herdbookRepository.retrieveHeredity(animal.id)
boolean heredityMatch = heredity != null
Collection<GenomicTestResource> testResources = breedingRepository.retrieveTests()
boolean existingTestMatch = testResources.find { test ->
test.animalId == animal.id
}
return genderMatch && heredityMatch && existingTestMatch
}
}
Is a genomic test allowed?version 2
(Warning: Groovy ahead!)
GenomicTestedBeforeCondition
class GenomicTestedBeforeCondition {
HerdbookRepository herdbookRepository
boolean isSatisfiedBy(Animal animal) {
final AnimalHeredityResource heredity = herdbookRepository.retrieveHeredity(animal.id)
return heredity?.heredity
}
}
GenomicTestRequestExistsCondition
class GenomicTestRequestExistsCondition {
BreedingRepository breedingRepository
boolean isSatisfiedBy(Animal animal) {
Collection<GenomicTestResource> testResources = breedingRepository.retrieveTests()
return testResources.find { test ->
test.animalId == animal.id
}
}
Animal
class Animal {
AnimalId id
String name
String gender
boolean isFemale() {
gender.toLowerCase() == 'female'
}
}
Putting them together
GenomicTestRequestAllowedCondition (1)
class GenomicTestRequestAllowedCondition {
GenomicTestedBeforeCondition genomicTestedBeforeCondition
GenomicTestRequestExistsCondition genomicTestRequestExistsCondition
...
GenomicTestRequestAllowedCondition (2)
class GenomicTestRequestAllowedCondition {
GenomicTestedBeforeCondition genomicTestedBeforeCondition
GenomicTestRequestExistsCondition genomicTestRequestExistsCondition
private Closure isFemale = { Animal animal -> animal.female }
private Closure notTestedBefore = { Animal animal -> !genomicTestedBeforeCondition.isSatisfiedBy(animal) }
private Closure noExistingGenomicTests = { Animal animal -> !genomicTestRequestExistsCondition.isSatisfiedBy(animal) }
GenomicTestRequestAllowedCondition (3)
class GenomicTestRequestAllowedCondition {
GenomicTestedBeforeCondition genomicTestedBeforeCondition
GenomicTestRequestExistsCondition genomicTestRequestExistsCondition
private Closure isFemale = { Animal animal -> animal.female }
private Closure notTestedBefore = { Animal animal -> !genomicTestedBeforeCondition.isSatisfiedBy(animal) }
private Closure noExistingGenomicTests = { Animal animal -> !genomicTestRequestExistsCondition.isSatisfiedBy(animal) }
boolean isSatisfiedBy(Animal a) {
isFemale(a) && notTestedBefore(a) && noExistingGenomicTests(a)
}
}
Testing easier
Individual specification
class GenomicTestedBeforeConditionSpec extends Specification {
void "test condition should check that animal is tested before"() {
given:
GenomicTestedBeforeCondition condition = new GenomicTestedBeforeCondition()
when: "there are heredity characteristics"
condition.herdbookRepository = Mock(HerdbookRepository) {
1 * retrieveHeredity(_) >> new AnimalHeredityResource()
}
then: "animal must have been tested before"
condition.isSatisfiedBy(SOME_ANIMAL)
}
}
Combination of specifications
class GenomicTestRequestAllowedConditionSpec extends Specification {
GenomicTestRequestAllowedCondition condition = new GenomicTestRequestAllowedCondition()
void "test condition should be satisfied if all subconditions are met"() {
when: "all subconditions are met"
condition.isFemale = { true }
condition.notTestedBefore = { true }
condition.noExistingGenomicTests = { true }
then: "the condition is satisified"
condition.isSatisfiedBy(new Animal())
when: "one of the subconditions is not met"
condition.notTestedBefore = { false }
……...
Remember these closures? :-)
private Closure isFemale = { Animal animal -> animal.female }
private Closure notTestedBefore = { Animal animal -> !genomicTestedBeforeCondition.isSatisfiedBy(animal) }
private Closure noExistingGenomicTests = { Animal animal -> !genomicTestRequestExistsCondition.isSatisfiedBy(animal) }
That's it!
Thank you