Development Craftsmanship
Agenda
- Object Oriented Programming
- Clean Code
- Migrations
- Feature Toggles
- Testing
- Logging & Monitoring
- CI/CD
- MVP
Hey, I'm Boaz
Full Stack Developer
4.5 years in Shavit
Object Oriented Programming
Abstractions & Encapsulation
method signatures
public class Random {
public byte[] generate(int length) {
...
}
}
Polymorphism & Inheritance
overload an action
public class SecureRandom extends Random {
public byte[] generate(int length) {
...
}
}
Principles
Good OOP
Single Responsibility
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
public class StringProcessor {
private final StringReader stringReader;
private final StringWriter stringWriter;
public StringProcessor(StringReader stringReader,
StringWriter stringWriter) {
this.stringReader = stringReader;
this.stringWriter = stringWriter;
}
public void printString() {
stringWriter.write(stringReader.getValue());
}
}
Composition Over Inheritance
Advanced Topics
In Software
Clean Code
No abbreviations
public int cntEvn(Collection<int> cln) {
int cnt = 0;
for(int i : cln) {
if(i % 2 == 0) {
cnt++;
}
}
return cnt;
}
public int countEvenItems(Collection<int> collection) {
int evenItems = 0;
for(int i : collection) {
if(item % 2 == 0) {
evenItems++;
}
}
return evenItems;
}
Be Consistent
(No synonyms)
public void countThings(Collection<int> things) {
...
}
public void numerateThings(Collection<int> things) {
...
}
You Ain't Gonna Need It (YAGNI)
Occam's razor /
Keep It Simple Stupid
Don't Repeat Yourself but Write Everything Twice
Database Migrations
(Go) Migrate
mongodb-migrations
Maven (Java):
src/main/resources/db/migration/V1/V1.0__Create_Table_users.sql
CREATE TABLE users (
id bigint(20) NOT NULL AUTO_INCREMENT,
username varchar(100) NOT NULL,
first_name varchar(50) NOT NULL,
last_name varchar(50) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY UK_username (username)
) DEFAULT CHARSET=utf8;
src/main/resources/db/migration/V1/V1.1__Create_Table_admins.sql src/main/resources/db/migration/V2/V2.0__Create_Table_things.sql
Never rename.
Add column, n versions later remove old column.
Feature Toogles & Experiments
function reticulateSplines(){
// current implementation lives here
}
function reticulateSplines(){
var useNewAlgorithm = false;
// useNewAlgorithm = true; // UNCOMMENT IF YOU ARE WORKING
// ON THE NEW SR ALGORITHM
if( useNewAlgorithm ){
return enhancedSplineReticulation();
}else{
return oldFashionedSplineReticulation();
}
}
function oldFashionedSplineReticulation(){
// current implementation lives here
}
function enhancedSplineReticulation(){
// TODO: implement better SR algorithm
}
How do I introduce change?
Experiments
A/B Testing
Canary Release
Stateless
Push state to the boundaries (Databases)
Testing
End To End Tests
(System Tests)
- Slowest
- Focus on making sure no major breaks
- No edge cases
- Close to business language
public class GoogleSearch {
static WebDriver driver = new FirefoxDriver();
static Wait<WebDriver> wait = new WebDriverWait(driver, MAX_30_SECONDS);
public static void main(String[] args) {
boolean result;
driver.get("http://www.google.com/");
try {
result = firstPageContainsQAANet();
} catch(Exception e) {
result = false;
} finally {
driver.close();
}
System.out.println("Test " + (result? "passed." : "failed."));
}
private static boolean firstPageContainsQAANet() {
//type search query
driver.findElement(By.name("q")).sendKeys("qa automation\n");
// click search
driver.findElement(By.name("btnG")).click();
// Wait for search to complete
wait.until(webDriver -> {
System.out.println("Searching ...");
return webDriver.findElement(By.id("resultStats")) != null;
});
// Look for QAAutomation.net in the results
return driver.findElement(By.tagName("body")).getText().contains("qaautomation.net");
}
}
Integration Tests
(Black-Box Testing)
- Slower
- Focus on validation correctness of single service at a time
- Validate correctness, not code quality
- Boost confident while preserving test isolation and speed
@Test
public class SomeKeyGoogleService {
public SearchServiceConnection searchService;
@Before
public void init() {
// Initialize service
searchService = ...;
}
@After
public void clean() {
// Do some cleaning
}
@Describe("Given Youtube service has results and there are existing indexed pages " +
"When a phrase searched Then return Youtube with indexed results")
public void usuallyThisWillBeAMeaningfullName() {
// given
youtubeMock.when("/searchResults?search=my%20search%20phrase")
.thenReturn("{video: \"http://youtube.com/v?asd123@!ASd2134=\"}");
db.insert(new IndexedPage("Welcome to my search phrase"));
// when
var returnContent = searchService.call("/search?s=my%20search%20phrase").result().asJson();
// then
expect(returnContent).toConsistOf(youtubeVideo("http://youtube.com/v?asd123@!ASd2134="),
pageInfo("Welcome to my search phrase"));
}
}
Unit
- Fastest
- Focus on validation correctness of single [public] function at a time
- Validate mostly code quality, but also correctness as a side effect*
- Encourage clean code
- Easiest to write
public class AlertAdderTest
{
public SlackAlertAdder slackAlertAdder = mock(SlackAlertAdder.class);
public FacebookAlertAdder facebookAlertAdder = mock(FacebookAlertAdder.class);
public AlertAdder alertAdder = new AlertAdder(slackAlertAdder, facebookAlertAdder);
@Test
public void whatAnAlertIsAddedThenSlackAlertIsAdded()
{
Alert anAlert = new Info("Holy shit, watch out");
alertAdder.add(anAlert);
verify(slackAlertAdder).add(anAlert);
}
}
Also
Consumer Driven Contract
(PACT)
Component Testing
Mutation Testing
(PIT)
Test Driven Development
TDD
Understood?
NO
TDD Cycle
- Failing test
- Minimal implementation
- Refactor
Understood?
Yes
TDD Cycle
- Write the smallest possible failing test.
- Write the most naive implementation possible.
- Refactor for a better quality code.
Understood?
Yes
Central Logging
Produce -> Collect -> Index -> Use
Service-wise
Fluentd/ Logstash
Elasticsearch
Kibana
Also: Correlation Ids, Schema
Kibana Example
Tracing
Using correlation Ids we can create a flow of the data:
Monitoring
Counter counter = Counter
.builder("instance")
.description("indicates instance count of the object")
.tags("dev", "performance")
.register(registry);
counter.increment(2.0);
assertTrue(counter.count() == 2);
counter.increment(-1);
assertTrue(counter.count() == 2);
SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = registry.timer("app.event");
timer.record(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException ignored) { }
});
timer.record(3000, MILLISECONDS);
assertTrue(2 == timer.count());
assertTrue(4510 > timer.totalTime(MILLISECONDS)
&& 4500 <= timer.totalTime(MILLISECONDS));
Micrometer (Java)
What to monitor?
Business Metrics
A Key Performance Indicator (KPI)
Mean time to failure (MTTF)
Mean Time to Repair (MTTR)
Continues Integration
Continues Deployment
Continues Delivery
Automation Server (Jenkins)
pipeline {
agent any
tools {
maven 'Maven 3.3.9'
jdk 'jdk8'
}
stages {
stage ('Initialize') {
steps {
sh '''
echo "PATH = ${PATH}"
echo "M2_HOME = ${M2_HOME}"
'''
}
}
stage ('Build') {
steps {
sh 'mvn -Dmaven.test.failure.ignore=true install'
}
post {
success {
junit 'target/surefire-reports/**/*.xml'
}
}
}
}
}
Automation Server (CircleCI)
version: 2
jobs:
build:
docker:
- image: circleci/<language>:<version TAG>
steps:
- checkout
- run: echo "hello world"
deploy:
docker:
- image: java:8-jre-alpine
steps:
- run: java -jar my-app-*.jar
FROM alpine/git
WORKDIR /app
RUN git clone https://github.com/spring-projects/spring-petclinic.git
FROM maven:3.5-jdk-8-alpine
WORKDIR /app
COPY --from=0 /app/spring-petclinic /app
RUN mvn install
FROM openjdk:8-jre-alpine
WORKDIR /app
COPY --from=1 /app/target/spring-petclinic-1.5.1.jar /app
CMD ["java -jar spring-petclinic-1.5.1.jar"]
Docker
Kubernetes
Microservices
Single application as a suite of small services.
Service is built around business capabilities and independently deploy-able.
N-Tier Architecture
Microservices
Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.
-- Melvyn Conway, 1967
Domain Driven Design
How do I "built around business capabilities"
Bounded Context
Ubiquitous Language
Minimum Viable Product
Build a simple solution, put it in front of customers, enhance incrementally based on customer feedback.
Resources
- Clean Code, Robert C. Martin
- Growing Object Oriented Software, guided by tests- Steve Freeman & Nat Pryce
- The Twelve Factor App- https://12factor.net/
- Domain Driven Design- Eric Evans
- Building Microservices- Sam Newman
- The Pheonix Project- Gene Kim, Kevin Behr, George Spafford
- Continues Delivery- Jez Humble & David Farley
- Medium- https://medium.com/
- StackShare.io- https://stackshare.io/
- Newsletters- SWLW, Alligator.io, DevOpsLinks
Development Craftsmanship
By Boaz Berman
Development Craftsmanship
- 217