Skip to content
This repository has been archived by the owner on Apr 20, 2022. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
marcingrzejszczak committed May 4, 2016
0 parents commit 3931cb5
Show file tree
Hide file tree
Showing 34 changed files with 1,075 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .gitignore
@@ -0,0 +1,20 @@
*~
#*
*#
.#*
.classpath
.project
.settings/
.springBeans
target/
_site/
.idea
*.iml
*.swp
.factorypath
*.logtjmeter
.checkstyle
.DS_Store
*.log
/spring-cloud-sleuth-core/nb-configuration.xml
/spring-cloud-sleuth-core/nbactions.xml
111 changes: 111 additions & 0 deletions README.adoc
@@ -0,0 +1,111 @@
= Database Update

How to write the code of your app to allow doing A/B testing or/and continuous deployment.

== Assumptions

In order for the process to be relatively simple the following assumptions need to take place
while developing the app:

- use a database versioning tool (we'll be using https://flywaydb.org[Flyway])
- the database changes need to be backwards compatible
- if you want to do a backwards incompatible change you have to do it in a couple of deployments
- we don't want to do DB rollbacks. Not doing them simplifies the deployment process (some rollbacks are close to impossible like rolling back a delete)
- we want to ALWAYS be able to rollback the application one version back (not more)

== The issue

For readability we're versioning the apps with major increments.

Why is this problem worth discussing? Doing A/B testing with Cloud Foundry is extremely simple - it's a matter of executing a single command. However it's not that trivial in terms of the application and the db.

Let's take a look at the following example if you want to change the column name:

WARNING: The following example is deliberately done in such a way that it will break. We're showing it to depict the problem of database compatibility.

=== Changing the column name in backwards-incompatible way

Let's assume that we have the DB in version `v1`. It has the column with a name `last_name`. We want to change it to `surname.` We have the app in version `1.0.0` which uses the `last_name` column in the code.

==== Rolling a new backwards-incompatible version

. in Flyway we're writing a script to change the column name from `last_name` to `surname`
. we're writing our code to use the new `surname` column

==== A/B testing

The current situation is that we have an app deployed to production in version `1.0.0` and db in `v1`. We want to deploy the second instance of the app that will be in version `2.0.0` and update the db to `v2`.

Steps:

. a new instance is deployed in version `2.0.0` that updates the db to `v2`
. in `v2` of the database the column `last_name` is no longer existing - it got changed to `surname`
. the db and app upgrade is successful and you have some instances working in `1.0.0`, others in `2.0.0`. All are talking to db in `v2`
. all instances of version `1.0.0` will start producing exceptions cause they will try to insert data to `last_name` column which is no longer there
. all instances of version `2.0.0` will work without any issues

As you can if we do backwards incompatible changes of the DB and the application, A/B testing is impossible.

==== Rolling back the application

Let's assume that after trying to do A/B deployment we've decided that we need to rollback the app back to version `1.0.0`. We assumed that we don't want to roll back the database.

Steps:

. we shut down the instance that was running with version `2.0.0`
. the database is still in `v2`
. since version `1.0.0` doesn't understand what `surname` column is it will produce exceptions
. hell broke loose and we can't go back

As you can if we do backwards incompatible changes of the DB and the application, we can't roll back to a previous version.

== Proper database update flow

INFO: Here we'll present a more sound approach to deployment

=== Adding a new column

This situation is quite simple. Let's assume that we have the DB in version `v1`. It doesn't have the column `surname`.
We also have the app in version `1.0.0` which doesn't use the `surname` column.

==== Rolling a new version

Steps:

. migrate your db to create the new column called `surname`. Now your db is in `v2`
. write the code to use the new column. Now your app is in version `2.0.0`

If you're using Spring Boot Flyway those two steps will be performed upon booting the version `2.0.0` of the app. If you're running database versioning tool manually then you'd have to do it in separate processes (first manually upgrade the db version and then deploy the new app).

==== A/B testing

The current situation is that we have an app deployed to production in version `1.0.0` and db in `v1`. We want to deploy the second instance of the app that will be in version `2.0.0` and update the db to `v2`.

Steps:

. a new instance is deployed in version `2.0.0` that updates the db to `v2`
. in the meantime some requests got processed by instances being in version `1.0.0`
. the upgrade is successful and you have some instances working in `1.0.0`, others in `2.0.0`. All are talking to db in `v2`
. version `1.0.0` is not using the database's column `surname` and version `2.0.0` is. They don't interfere each other, no exceptions should be thrown.

==== Rolling back the application

The current situation is that we have app in version `2.0.0` and db in `v2`.

Steps:

. roll back your app to version `1.0.0`.
. version `1.0.0` is not using the database's column `surname` thus rollback should be successful

== Projects

We will focus on the most interesting case of changing the column name. That change is backwards
incompatible but we'll try to write it in such a way that A/B testing is possible.

[source,bash]
-------
├── boot-flyway-v1 - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2 - 2.0.0 version of the app with v2 of the schema (backwards-compatible - app can be rolled back)
├── boot-flyway-v2-bad - 2.0.0 version of the app with v2 of the schema (backwards-incompatible - app can't be rolled back)
└── boot-flyway-v3 - 3.0.0. version of the app with v3 of the schema (app can be rolled back)
-------
9 changes: 9 additions & 0 deletions boot-flyway-v1/README.adoc
@@ -0,0 +1,9 @@
== Spring Boot Flyway Sample

This sample demonstrates the flyway auto-configuration support.

You can look at `http://localhost:8080/flyway` to review the list of scripts.

This sample also enables the H2 console (at `http://localhost:8080/h2-console`)
so that you can review the state of the database (the default jdbc url is
`jdbc:h2:mem:testdb`).
59 changes: 59 additions & 0 deletions boot-flyway-v1/pom.xml
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
</parent>
<artifactId>spring-boot-sample-flyway</artifactId>
<name>Spring Boot Flyway Sample</name>
<description>Spring Boot Flyway Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
52 changes: 52 additions & 0 deletions boot-flyway-v1/src/main/java/sample/flyway/Person.java
@@ -0,0 +1,52 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;

public String getFirstName() {
return this.firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return this.lastName;
}

public void setLastName(String lastname) {
this.lastName = lastname;
}

@Override
public String toString() {
return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
+ "]";
}
}
25 changes: 25 additions & 0 deletions boot-flyway-v1/src/main/java/sample/flyway/PersonRepository.java
@@ -0,0 +1,25 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.flyway;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends CrudRepository<Person, Long> {

}
@@ -0,0 +1,39 @@
/*
* Copyright 2012-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.flyway;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SampleFlywayApplication implements CommandLineRunner {

@Autowired
private PersonRepository repository;

@Override
public void run(String... args) throws Exception {
System.err.println(this.repository.findAll());
}

public static void main(String[] args) throws Exception {
SpringApplication.run(SampleFlywayApplication.class, args);
}

}
3 changes: 3 additions & 0 deletions boot-flyway-v1/src/main/resources/application.properties
@@ -0,0 +1,3 @@
spring.jpa.hibernate.ddl-auto=validate

spring.h2.console.enabled=true
7 changes: 7 additions & 0 deletions boot-flyway-v1/src/main/resources/db/migration/V1__init.sql
@@ -0,0 +1,7 @@
CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');
@@ -0,0 +1,42 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package sample.flyway;

import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertEquals;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(SampleFlywayApplication.class)
public class SampleFlywayApplicationTests {

@Autowired
private JdbcTemplate template;

@Test
public void testDefaultSettings() throws Exception {
assertEquals(new Integer(1), this.template
.queryForObject("SELECT COUNT(*) from PERSON", Integer.class));
}

}
9 changes: 9 additions & 0 deletions boot-flyway-v2-bad/README.adoc
@@ -0,0 +1,9 @@
== Spring Boot Flyway Sample

This sample demonstrates the flyway auto-configuration support.

You can look at `http://localhost:8080/flyway` to review the list of scripts.

This sample also enables the H2 console (at `http://localhost:8080/h2-console`)
so that you can review the state of the database (the default jdbc url is
`jdbc:h2:mem:testdb`).

0 comments on commit 3931cb5

Please sign in to comment.