Upload
agile-lietuva
View
795
Download
2
Embed Size (px)
DESCRIPTION
Osvaldo Grigo ir Vadim Platonov pranešimas "Kodas ir specifikacija: du zuikiai vienu šūviu" skaitytas Agile dienoje 2013 gegužės 9 d. Kaip Behavior-Driven Development idėjas pritaikėme kurdami finansinių paslaugų valdymo sistemą. Kaip galima kodą paversti pirminiu tiesos šaltiniu, kai automatizuotais testais užrašomi reikalavimai. Trumpai supažindinsime su mūsų architektūros specifika, paremta DDD, CQRS ir Event Sourcing principais, kurie atveria naujas galimybes, bet kartu atneša naujų iššūkių. Kokias darėme klaidas, ir kaip jų išvengti.
Citation preview
Kodas ir specifikacija
du zuikiai vienu šūviu
code spec
Osvaldas GrigasVadim Platonov
What happens next
● Motivation● Case study● Live demo
Specification?
Requirements?
Scenarios?
Documentatio
n?
User stories?
What do we need?
Test cases?
Use cases?
Code?
Examples?Acceptance criteria?
Start with the code?
Requirements?
Specifications?
Test Cases?
code
Specification
Requirements
Test Cases
What did we try?
Behavior-Driven Development
BDD?
Domain experts + DevsSpecify scenarios in plain text
DevsAutomate scenarios as tests
JenkinsPublish scenarios to Wiki
Domain expertsDescribe features
Let's Make a Payment.
Domain expertsDescribe a feature
Account can only be debited if it has sufficient balance and is not blocked for debit.
Domain experts + DevsSpecify scenarios
Scenario: Debiting an account with sufficient balance
Given a liability account LT000001 with a balance of 100 USD,
not blocked for debit
When the account LT000001 is debited for 20 USD
Then the debit succeeds producing a running balance of 80 USD
DevsAutomated scenarios as tests
public class DebitAccount extends JUnitStory {
@Given("a $type account $number with a balance of $balance USD, $debitStatus for debit") public void givenAnAccount(String type, String number, int balance, String debitStatus) { // ... }
@When("the account $number is debited for $amount USD") public void whenAccountIsDebitedBy(String number, int amount) { // ... }
@Then("the debit $result producing a running balance of $balance USD") public void thenDebitResultsIn(String result, int balance) { // ... }}
Room for improvement
Developers
have to maintain stories, test code and their mapping
COOL
What if...
specifications were generated from test code?
Business language
● gets lost in implementation details
● is not enforced in code
COOL
What if...
core parts of production code were reflected
in specifications?
Specifyscenarios
in plain text
Automatescenariosas tests
Specifyscenarios
in code
Story: Debiting an account
Scenario: Sufficient balance
Given: Account with
● id: LT000001● balance: USD 100● balance type: Liability● additional currency support: Disallowed● debiting allowed: Yes● crediting allowed: Yes
When: Debit account with
● account number: LT000001● amount: 20● currency: USD
Then: Account debited
● with amount 20● with running balance 80● with currency "USD"
code
Can I do that?
code
Yes I can! *
public class Debiting_an_account extends Story<Account, DebitAccount> {
@Test public void sufficient_balance() { givenEvents( new AccountCreated("LT000001", "USD", LIABILITY), accountCredited("LT000001", "any", $(30, "USD")), accountCredited("LT000001", "any", $(70, "USD")));
onCommand(new DebitAccount("LT000001", "any", $(20), "USD"));
expectEvent(AccountDebited.class) .with("amount", $(20)) .with("runningBalance", $(80)) .with("currency", "USD"); }
}
DDD? CQRS?Event
Sourcing?
Domain-Driven DesignCrash Course
DDD
Ubiquitous Language
Account
Debit
CreditAsset
Liability
Balance
public class Debiting_an_account extends Story<Account, DebitAccount> {
@Test public void sufficient_balance() { givenEvents( new AccountCreated("LT000001", "USD", LIABILITY), accountCredited("LT000001", "any", $(30, "USD")), accountCredited("LT000001", "any", $(70, "USD")));
onCommand(new DebitAccount("LT000001", "any", $(20), "USD"));
expectEvent(AccountDebited.class) .with("amount", $(20)) .with("runningBalance", $(80)) .with("currency", "USD"); }
}
DDD
Entity
public class Account extends AggregateRoot {
private Balance balance;
public void credit(Amount amount) { ... }
public void debit(Amount amount) { ... }
}
public class Debiting_an_account extends Story<Account, DebitAccount> {
@Test public void sufficient_balance() { givenEvents( new AccountCreated("LT000001", "USD", LIABILITY), accountCredited("LT000001", "any", $(30, "USD")), accountCredited("LT000001", "any", $(70, "USD")));
onCommand(new DebitAccount("LT000001", "any", $(20), "USD"));
expectEvent(AccountDebited.class) .with("amount", $(20)) .with("runningBalance", $(80)) .with("currency", "USD"); }
}
DDD
Isolated Domain
Domain
data storemessage
broker
client services
Event Sourcing Crash Course
Events
Capture all changes to domain state
public class AccountDebited {
public String accountNumber;
public BigDecimal amount;
public BigDecimal runningBalance;
public String currency;
}
Events
Never lose data
AccountCreated 0
AccountCredited 300
AccountCredited 1000
AccountCredited 1500
AccountDebited 900
AccountCredited 1200
AccountDebited 800
AccountDebited 700
time
Balance
public class Debiting_an_account extends Story<Account, DebitAccount> {
@Test public void sufficient_balance() { givenEvents( new AccountCreated("LT000001", "USD", LIABILITY), accountCredited("LT000001", "any", $(30, "USD")), accountCredited("LT000001", "any", $(70, "USD")));
onCommand(new DebitAccount("LT000001", "any", $(20), "USD"));
expectEvent(AccountDebited.class) .with("amount", $(20)) .with("runningBalance", $(80)) .with("currency", "USD"); }
}
Command-QueryResponsibilitySegregation
Crash
Course
Read Model
CQRS
Write Model
QueryCommand
CQRS
Command
public class DebitAccount {
public final String accountNumber;
public final BigDecimal amount;
public final String currency;
}
public class Debiting_an_account extends Story<Account, DebitAccount> {
@Test public void sufficient_balance() { givenEvents( new AccountCreated("LT000001", "USD", LIABILITY), accountCredited("LT000001", "any", $(30, "USD")), accountCredited("LT000001", "any", $(70, "USD")));
onCommand(new DebitAccount("LT000001", "any", $(20), "USD"));
expectEvent(AccountDebited.class) .with("amount", $(20)) .with("runningBalance", $(80)) .with("currency", "USD"); }
}
Putting it all together
MongoDBDomain
(Aggregates)
Projectors (Groovy)
Command Handlers
Query Services
Client (Wicket)
Event StoreEvents
Events
Commands Projections
Projections
Events
Domain (Aggregates)
Command Handlers
User Story Tests
Events
Commands
MockEvent Store
Events
Events
Testingthe Domain
Live DemoCoding Specifications
Attributions
Photo: Accidents - after crash 1978 SkodaAuthor: Istvan TakacsURL: http://commons.wikimedia.org/wiki/File:Accidents_-_after_crash_1978_Skoda.jpgLicense: http://creativecommons.org/licenses/by-sa/3.0/deed.en
Photo: Tram in Kraków, PolandAuthor: AstrorekURL: http://commons.wikimedia.org/wiki/File:Tramwaj_krakow_3.jpgLicense: http://creativecommons.org/licenses/by-sa/3.0/deed.en
Photo: Mokate 3in1© Copyright 2013 Mokate S.A.URL: http://www.mokate.eu/
Image: JBehave examples@ Copyright JBehaveURL: http://jbehave.org/
Image: Rainbow@ Copyright MoonglowlillyURL: http://moonglowlilly.deviantart.com/art/PNG-RAINBOW-324144428
Image: Debit cardURL: http://www.capitalistbanter.com/2011/05/featured/the-best-and-worst-time-to-use-a-debit-card/