Thoughts on Software Reflexiones sobre cómo dearrollar software y no morir en el intento

Continuous Delivery Cambiando nuestro código sin dejar de ofrecer un servicio

En este artículo presentamos un ejemplo práctico de cómo llevar a cabo cambios importantes tanto en nuestro código como en la base de datos utilizada para mantener la persistencia de la informació sin dejar de proporcionar servicio. De esta forma, podemos hacer efectivo el paradigma de continuous delivery sin que los usuarios de nuestro sistema sufran paradas en el servicio debido a las actualizaciones que llevamos a cabo.

Presentación del ejemplo utilizado

Para mostrar de una forma efectiva las técnicas aqui expuestas, vamos a utilizar un servicio que gestiona información relativa a clientes expuesto a través de un API REST. Dicho servicio guarda la siguiente información sobre los clientes:

  • Nombre
  • Apellidos
  • Calle
  • Número
  • Código postal
  • Ciudad

Estos datos son almacenados en una única tabla dentro de una base de datos relacional con la siguiente estructura:

Tabla para almacenar datos de clientes

Este servicio ha sido completamente implementado en Java, utilizando JPA para la gestión del almacenamiento persistente y Jersey para la creación de la interfaz REST. Existe una clase que modela a nuestros clientes:

package com.joragupra.domain;

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

@Entity(name = "customer")
public class Customer {

    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @Column(name = "street_name")
    private String streetName;
    @Column(name = "street_number")
    private String streetNumber;
    @Column(name = "postal_code")
    private String postalCode;
    @Column(name = "city")
    private String city;

    public Customer() {
    }

    public Customer(String firstName, String lastName) {
        this(firstName, lastName, null, null, null, null);
    }

    public Customer(
            String firstName, String lastName, String streetName, String streetNumber, String postalCode, String city
    ) {
        this.firstName = firstName;
        this.lastName = lastName;
        updateAddress(streetName, streetNumber, postalCode, city);
    }

    public void updateAddress(String streetName, String streetNumber, String postalCode, String city) {
        this.streetName = streetName;
        this.streetNumber = streetNumber;
        this.postalCode = postalCode;
        this.city = city;
    }

    public Long id() {
        return id;
    }

    public String firstName() {
        return firstName;
    }

    public String lastName() {
        return lastName;
    }

    public String streetName() {
        return streetName;
    }

    public String streetNumber() {
        return streetNumber;
    }

    public String postalCode() {
        return postalCode;
    }

    public String city() {
        return city;
    }
}

Un repositorio de clientes:

package com.joragupra.domain;

public interface CustomerRepository {

    Customer findById(Long id);

    void save(Customer customer);

    long count();

}

Y un servicio de clientes:

package com.joragupra.domain;

public class CustomerService {

    private static CustomerService INSTANCE;

    private CustomerRepository repository;

    private CustomerService(CustomerRepository repository) {
        this.repository = repository;
    }

    public Customer retrieve(long customerId) {
        return this.repository.findById(customerId);
    }

    public Customer create(Customer customer) {
        if (customer.id() == null) {
            this.repository.save(customer);
        }
        return customer;
    }

    public Customer updateAddress(long customerId, String newStreetName, String newStringNumber, String newPostalCode, String newCity) {
        Customer customer = retrieve(customerId);

        if (customer == null) {
            throw new IllegalArgumentException("No customer found with ID " + customerId);
        }

        customer.updateAddress(newStreetName, newStringNumber, newPostalCode, newCity);
        this.repository.save(customer);
        return customer;
    }

    public static void init(CustomerRepository repository) {
        if (INSTANCE == null) {
            INSTANCE = new CustomerService(repository);
        }
    }

    public static CustomerService instance() {
        return INSTANCE;
    }

}

Con objeto de mantener el código lo más sencillo posible, he optado por no usar Spring (las principales dependencias siguen siendo inyectadas aunque esto lo hago de forma manual sin ayuda de ningún framework):

package com.joragupra;

import com.joragupra.persistence.CustomerRepositoryImpl;
import com.joragupra.persistence.DatabaseInitializer;
import com.joragupra.domain.CustomerService;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ServerInit implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {

        ...

        //here dependencies are injected
        CustomerService.init(new CustomerRepositoryImpl());
    }

    ...
}

Puedes ver el detalle de cómo se ha implementado la persistencia con JPA echándole un vistazo al código de las classes CustomerRepositoryImpl y PersistenceManager. También puedes descargar el código inicial de este proyecto aquí (utiliza la rama master).

El diseño de nuestra solución está lejos de ser perfecto por muchos motivos pero digamos que, hasta el momento, ha sido adecuado para nuestro propósito y lo estamos utilizando en producción. El código puede echarse a correr utilizando un servidor Jetty embebido arrancado con Maven:

mvn jetty:run -Djetty.port=[puerto donde queremos escuchar]

Si queremos que este servicio sea altamente disponible necesitaremos varias réplicas ejecutándose al mismo tiempo para que así un balanceador de carga pueda enviar las peticiones entrantes a cualquiera de las instancias en ejecución que considere oportuna (cómo configurar dicho balanceador de carga está fuera del alcance de esta entrada, aunque no es difícil).

Evolución del servicio de clientes

Una de las desventajas de esta solución es que solamente podemos conocer la dirección actual de un cliente, pero no las distintas direcciones que ha tenido a lo largo del tiempo. Así que decidimos que, de ahora en adelante, nuestro sistema va a almacenar un registro de todas las direcciones que ha usado un cliente junto con la fecha en que cada dirección ha sido proporcionada.

Esto va a requerir modificar tanto nuestra aplicación como el esquema de la base de datos relacional que usa. Existen varias formas de hacer esto. Por ejemplo, podemos pensar en un modelo de datos nuevo que dé soporte a los nuevos requisitos:

imagen aqui

Con una nueva tabla para direcciones (address) que tenga todos los campos actualmente contenidos en la tabla de cliente más una referencia (clave ajena) al cliente al que pertenece, modificamos nuestra aplicación y creamos un script de migración que cree la nueva tabla para direcciones con su correspondiente clave ajena a la tabla de clientes y migre los datos existentes, eliminando los campos de direcciones de la tabla de clientes ya que no son necesarios de ahora en adelante. Entonces paramos nuestros dos servidores en ejecución, actualizamos el código que ejecutan, actualizamos nuestra base de datos (cambio de esquema y migración de datos de clientes existentes) y volvemos a arrancar los servidores.

El principal inconveniente de esto es que nuestro servicio dejará de estar operativo el tiempo que dure la actualización de código y la migración de nuestra base de datos. Esto puede ser un gran inconveniente ya que, mientras la actualización del código tardará poco, la migración de los datos tendrá una duración variable, a menudo no conocida de antemano, y que dependerá en gran medida del volumen de datos que maneja nuestro servicio. Es decir, cuanto más datos tengamos en nuestra base de datos más tardaremos en migrarlos a la nueva estructura que le da soporte a los nuevos requisitos.

Es importante resaltar que, debido a la naturaleza del cambio que se quiere hacer, no es posible actualizar uno de los dos servidores y dejar el otro funcionando para mantener el servicio operativo durante la migración de datos. Sencillamente, el código nuevo no puede funcionar con el antiguo esquema de datos porque necesita la nueva tabla de direcciones, del mismo modo que el código antiguo no es compatible con el nuevo esquema porque necesita los campos de direcciones que hay en la tabla de clientes.

Por lo tanto, tenemos que adoptar un enfoque diferente si queremos hacer este cambio si queremos que siempre haya, al menos, un servidor en ejecución para poder garantizar que nuestro servicio está disponible. Para ello, debemos construir una secuencia de cambios compatibles que nos permitan mantener una versión antigua y otra actualizada de nuestro código en ejecución al mismo tiempo.

Cambiando nuestro código de forma compatible

Vamos a empezar añadiendo a la tabla de clientes una nueva columna para guardar la fecha de cambio de la dirección. Vamos a hacer que dicha columna pueda contener valores nulos y ni siquiera vamos a usarla desde nuestro código todavía. Simplemente, vamos a preparar nuestra base de datos para cuando queramos empezar a utilizarla. El cambio sería muy sencillo (y pequeño):

<changeSet id="customer-002" author="joragupra">

        <comment>Add address change date in customer table</comment>

        <addColumn tableName="customer">
            <column name="address_since" type="timestamp">
                <constraints nullable="true"/>
            </column>
        </addColumn>

        <rollback>
            <dropColumn tableName="customer" columnName="address_since"/>
        </rollback>
</changeSet>

Una de las utilidades que hemos añadido a nuestro código para hacer los cambios más rápidos es la capacidad de actualizar el esquema de datos a través del plugin de liquibase para Maven.

De esta forma, nuestro proceso de actualización de servidores consistirá en:

  • Parar un servidor
  • Actualizar el código (git pull)
  • Ejecutar el comando para migrar nuestra base de datos (mvn liquibase:update)
  • Arrancar el servidor (mvn jetty:run -Djetty.port=7070)
  • Parar el segundo servidor
  • Actualizar el código (git pull)
  • Arrancar el servidor (mvn jetty:run -Djetty.port=9090)

Cuando solamente hemos actualizado un servidor, podemos hacer varias llamadas a cada uno de los servidores (el que ya ha sido actualizado y el que todavía no lo ha sido) para comprobar que ambos funcionan correctamente, es decir, que el cambio es compatible con la versión antigua del código. Esto no es ninguna sorpresa ya que, hasta ahora, no hemos cambiado nada en el código, solamente hemos añadido una nueva columna a una tabla en nuestra base de datos.

curl http://localhost:7070/api/customers/1

{"currentAddress":{"city":null,"postalCode":null,"streetName":null,"streetNumber":null},"firstName":"Frank","lastName":"Freundlich"}

curl http://localhost:9090/api/customers/1

{"currentAddress":{"city":null,"postalCode":null,"streetName":null,"streetNumber":null},"firstName":"Frank","lastName":"Freundlich"}
endhighlight

A continuación, modificamos nuestra clase de clientes para, ahora sí empezar a
utilizar el nuevo campo que habíamos creado en la tabla de clientes:


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Customer</span> <span class="o">{</span>

    <span class="nd">@Id</span>
    <span class="nd">@GeneratedValue</span>
    <span class="kd">private</span> <span class="n">Long</span> <span class="n">id</span><span class="o">;</span>
    <span class="o">...</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"address_since"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">Date</span> <span class="n">addressSince</span><span class="o">;</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span>
            <span class="n">String</span> <span class="n">firstName</span><span class="o">,</span> <span class="n">String</span> <span class="n">lastName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span>
    <span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">firstName</span><span class="o">,</span> <span class="n">lastName</span><span class="o">,</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">city</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span>
            <span class="n">String</span> <span class="n">firstName</span><span class="o">,</span> <span class="n">String</span> <span class="n">lastName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">addressSince</span>
    <span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">firstName</span> <span class="o">=</span> <span class="n">firstName</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">lastName</span> <span class="o">=</span> <span class="n">lastName</span><span class="o">;</span>
        <span class="n">updateAddress</span><span class="o">(</span><span class="n">streetName</span><span class="o">,</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">city</span><span class="o">,</span> <span class="n">addressSince</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">updateAddress</span><span class="o">(</span><span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">addressChangeDate</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">streetName</span> <span class="o">=</span> <span class="n">streetName</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">streetNumber</span> <span class="o">=</span> <span class="n">streetNumber</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">postalCode</span> <span class="o">=</span> <span class="n">postalCode</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">city</span> <span class="o">=</span> <span class="n">city</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">addressSince</span> <span class="o">=</span> <span class="n">addressChangeDate</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="n">Date</span> <span class="nf">addressSince</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">addressSince</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span></code></pre></figure>


El servicio que actualiza los datos de clientes también es adaptado:


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomerService</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="n">CustomerRepository</span> <span class="n">repository</span><span class="o">;</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="n">Customer</span> <span class="nf">updateAddress</span><span class="o">(</span><span class="kt">long</span> <span class="n">customerId</span><span class="o">,</span> <span class="n">String</span> <span class="n">newStreetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">newStringNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">newPostalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">newCity</span><span class="o">)</span> <span class="o">{</span>
        <span class="n">Customer</span> <span class="n">customer</span> <span class="o">=</span> <span class="n">retrieve</span><span class="o">(</span><span class="n">customerId</span><span class="o">);</span>

        <span class="k">if</span> <span class="o">(</span><span class="n">customer</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"No customer found with ID "</span> <span class="o">+</span> <span class="n">customerId</span><span class="o">);</span>
        <span class="o">}</span>

        <span class="n">customer</span><span class="o">.</span><span class="na">updateAddress</span><span class="o">(</span><span class="n">newStreetName</span><span class="o">,</span> <span class="n">newStringNumber</span><span class="o">,</span> <span class="n">newPostalCode</span><span class="o">,</span> <span class="n">newCity</span><span class="o">,</span> <span class="k">new</span> <span class="n">Date</span><span class="o">());</span>
        <span class="k">this</span><span class="o">.</span><span class="na">repository</span><span class="o">.</span><span class="na">save</span><span class="o">(</span><span class="n">customer</span><span class="o">);</span>
        <span class="k">return</span> <span class="n">customer</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="o">...</span>

<span class="o">}</span></code></pre></figure>


Volvemos a actualizar nuestros dos servidores de forma secuencial, siempre
dejando, al menos, uno en ejecución. Podemos probar, por ejemplo, que los dos
servidores siguien funcionando cuando uno ya ha sido actualizado y el otro
todavía no (obviamente, sólo uno de ellos almacenará la fecha y hora de cambio
de la dirección de un cliente, pero los dos funcionarán).

Lo siguiente que haremos es publicar los datos acerca de cuándo ha cambiado una
dirección en nuestro servicio REST. Este cambio no debería representar ningún
problema, ya que consiste en añadir un nuevo campo en nuestro objecto DTO que
transporta la información relativa a direcciones y hacer un pequeño ajuste en la
clase que hace las transformaciones entre nuestras entidades y nuestro DTO:


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AddressDto</span> <span class="o">{</span>

    <span class="o">...</span>

    <span class="kd">private</span> <span class="n">Date</span> <span class="n">inUseSince</span><span class="o">;</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="nf">AddressDto</span><span class="o">(</span><span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">(</span><span class="n">streetName</span><span class="o">,</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">city</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="nf">AddressDto</span><span class="o">(</span><span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">inUseSince</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">streetName</span> <span class="o">=</span> <span class="n">streetName</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">streetNumber</span> <span class="o">=</span> <span class="n">streetNumber</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">postalCode</span> <span class="o">=</span> <span class="n">postalCode</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">city</span> <span class="o">=</span> <span class="n">city</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">inUseSince</span> <span class="o">=</span> <span class="n">inUseSince</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="n">Date</span> <span class="nf">getInUseSince</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">inUseSince</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setInUseSince</span><span class="o">(</span><span class="n">Date</span> <span class="n">inUseSince</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">inUseSince</span> <span class="o">=</span> <span class="n">inUseSince</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>

<span class="o">...</span>

<span class="kd">class</span> <span class="nc">CustomerMapper</span> <span class="o">{</span>

    <span class="kd">static</span> <span class="n">CustomerDto</span> <span class="nf">fromDomainToDto</span><span class="o">(</span><span class="n">Customer</span> <span class="n">c</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">CustomerDto</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">firstName</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">lastName</span><span class="o">(),</span>
                               <span class="k">new</span> <span class="nf">AddressDto</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">streetName</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">streetNumber</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">postalCode</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">city</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">addressSince</span><span class="o">())</span>
        <span class="o">);</span>
    <span class="o">}</span>

    <span class="o">...</span>

<span class="o">}</span></code></pre></figure>


Ahora sí podemos empezar a hacer los grandes cambios que requiere el
mantenimiento de un historial de direcciones. Será bastante complicado hacer
esto manteniendo toda la información relativa a las direcciones en la misma
clase que modela a cliente y almacenando dicha información en la misma tabla en
nuestra base de datos.

Vamos a optar por crear una nueva clase que modele direcciones (Address) y
persistirla en nuestra base de datos en una tabla independiente, distinta de la
tabla de clientes. Sin embargo, como ya vimos antes, no podemos hacer todos los
cambios al mismo tiempo si queremos mantener la compatibilidad entre distintas
versiones que se estén ejecutando en nuestros servidores al mismo tiempo
(recordemos que, durante el proceso de actualización de nuestros servidores,
habrá un periodo de tiempo en el que un servidor ya ha sido actualizado y el
otro todavia no, por lo que ambas versiones deben ser compatibles). Por ello,
vamos a proceder del siguiente modo: primero haremos los cambios necesarios en
nuestras clases (objetos en memoria) y, a continuación, los cambios necesarios
en nuestras tablas (datos persistentes).

Así que vamos a crear una nueva clase para direcciones y vamos a hacer que la
clase de clientes empiece a a trabajar con dichas direcciones manteniendo un
historial de las mismas:


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kn">package</span> <span class="n">com</span><span class="o">.</span><span class="na">joragupra</span><span class="o">.</span><span class="na">domain</span><span class="o">;</span>

<span class="kn">import</span> <span class="nn">java.util.Date</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Address</span> <span class="o">{</span>

    <span class="kd">private</span> <span class="n">String</span> <span class="n">streetName</span><span class="o">;</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">;</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">;</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">city</span><span class="o">;</span>
    <span class="kd">private</span> <span class="n">Date</span>   <span class="n">addressSince</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">Address</span><span class="o">(</span><span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">addressSince</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">streetName</span> <span class="o">=</span> <span class="n">streetName</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">streetNumber</span> <span class="o">=</span> <span class="n">streetNumber</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">postalCode</span> <span class="o">=</span> <span class="n">postalCode</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">city</span> <span class="o">=</span> <span class="n">city</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">addressSince</span> <span class="o">=</span> <span class="n">addressSince</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">String</span> <span class="nf">streetName</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">streetName</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">String</span> <span class="nf">streetNumber</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">streetNumber</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">String</span> <span class="nf">postalCode</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">postalCode</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">String</span> <span class="nf">city</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">city</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">Date</span> <span class="nf">addressSince</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">addressSince</span><span class="o">;</span>
    <span class="o">}</span>

<span class="o">}</span>

<span class="o">...</span>

<span class="nd">@Entity</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"customer"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Customer</span> <span class="o">{</span>

    <span class="o">...</span>

    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"city"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">city</span><span class="o">;</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"address_since"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">Date</span> <span class="n">addressSince</span><span class="o">;</span>
    <span class="nd">@Transient</span>
    <span class="kd">private</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span> <span class="n">addressHistory</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;();</span>

    <span class="kd">public</span> <span class="nf">Customer</span><span class="o">(</span>
            <span class="n">String</span> <span class="n">firstName</span><span class="o">,</span> <span class="n">String</span> <span class="n">lastName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">addressSince</span>
    <span class="o">)</span> <span class="o">{</span>
        <span class="k">this</span><span class="o">.</span><span class="na">firstName</span> <span class="o">=</span> <span class="n">firstName</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">lastName</span> <span class="o">=</span> <span class="n">lastName</span><span class="o">;</span>
        <span class="n">updateAddress</span><span class="o">(</span><span class="n">streetName</span><span class="o">,</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">city</span><span class="o">,</span> <span class="n">addressSince</span><span class="o">);</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">updateAddress</span><span class="o">(</span><span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">addressChangeDate</span><span class="o">)</span> <span class="o">{</span>
        <span class="o">...</span>
        <span class="k">this</span><span class="o">.</span><span class="na">city</span> <span class="o">=</span> <span class="n">city</span><span class="o">;</span>
        <span class="k">this</span><span class="o">.</span><span class="na">addressSince</span> <span class="o">=</span> <span class="n">addressChangeDate</span><span class="o">;</span>

        <span class="k">this</span><span class="o">.</span><span class="na">addressHistory</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">Address</span><span class="o">(</span><span class="n">streetName</span><span class="o">(),</span> <span class="n">streetNumber</span><span class="o">(),</span> <span class="n">postalCode</span><span class="o">(),</span> <span class="n">city</span><span class="o">(),</span> <span class="n">addressSince</span><span class="o">()));</span>
    <span class="o">}</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="n">Address</span> <span class="nf">currentAddress</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">Address</span><span class="o">(</span><span class="n">streetName</span><span class="o">(),</span> <span class="n">streetNumber</span><span class="o">(),</span> <span class="n">postalCode</span><span class="o">(),</span> <span class="n">city</span><span class="o">(),</span> <span class="n">addressSince</span><span class="o">());</span>
    <span class="o">}</span>

    <span class="kd">public</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span> <span class="nf">addressHistory</span><span class="o">()</span> <span class="o">{</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span> <span class="n">addressHistoryCopy</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;();</span>
        <span class="n">addressHistoryCopy</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">Address</span><span class="o">(</span><span class="n">streetName</span><span class="o">(),</span> <span class="n">streetNumber</span><span class="o">(),</span> <span class="n">postalCode</span><span class="o">(),</span> <span class="n">city</span><span class="o">(),</span> <span class="n">addressSince</span><span class="o">()));</span>
        <span class="k">return</span> <span class="n">addressHistoryCopy</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="o">...</span>

<span class="o">}</span>

<span class="o">...</span>

<span class="kd">class</span> <span class="nc">CustomerMapper</span> <span class="o">{</span>

    <span class="kd">static</span> <span class="n">CustomerDto</span> <span class="nf">fromDomainToDto</span><span class="o">(</span><span class="n">Customer</span> <span class="n">c</span><span class="o">)</span> <span class="o">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nf">CustomerDto</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">firstName</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">lastName</span><span class="o">(),</span>
                               <span class="k">new</span> <span class="nf">AddressDto</span><span class="o">(</span><span class="n">c</span><span class="o">.</span><span class="na">currentAddress</span><span class="o">().</span><span class="na">streetName</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">currentAddress</span><span class="o">().</span><span class="na">streetNumber</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">currentAddress</span><span class="o">().</span><span class="na">postalCode</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">currentAddress</span><span class="o">().</span><span class="na">city</span><span class="o">(),</span> <span class="n">c</span><span class="o">.</span><span class="na">currentAddress</span><span class="o">().</span><span class="na">addressSince</span><span class="o">())</span>
        <span class="o">);</span>
    <span class="o">}</span>

<span class="o">}</span></code></pre></figure>


Como podemos ver, el cambio todavía no es completo, pues el historial de
direcciones no se está guardando realmente de forma persistente, pero nuestro
modelo en memoria ya está preparado para gestionar un historial de direcciones
correctamente y, aún más importante, es totalmente compatible con el modelo que
tenemos en nuestra base de datos.

Ahora vamos a crear la estructura necesaria en nuestra base de datos para
persistir el historial de direcciones con una nueva tabla:


<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt">&lt;changeSet</span> <span class="na">id=</span><span class="s">"customer-003"</span> <span class="na">author=</span><span class="s">"joragupra"</span><span class="nt">&gt;</span>

        <span class="nt">&lt;comment&gt;</span>Address history management<span class="nt">&lt;/comment&gt;</span>

        <span class="nt">&lt;createTable</span> <span class="na">tableName=</span><span class="s">"address"</span> <span class="na">schemaName=</span><span class="s">"public"</span><span class="nt">&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"id"</span> <span class="na">autoIncrement=</span><span class="s">"true"</span> <span class="na">type=</span><span class="s">"bigint"</span><span class="nt">&gt;</span>
                <span class="nt">&lt;constraints</span> <span class="na">primaryKey=</span><span class="s">"true"</span> <span class="na">nullable=</span><span class="s">"false"</span>
                             <span class="na">primaryKeyName=</span><span class="s">"pk_addresses"</span> <span class="nt">/&gt;</span>
            <span class="nt">&lt;/column&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"street_name"</span> <span class="na">type=</span><span class="s">"text"</span><span class="nt">/&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"street_number"</span> <span class="na">type=</span><span class="s">"text"</span><span class="nt">/&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"postal_code"</span> <span class="na">type=</span><span class="s">"text"</span><span class="nt">/&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"city"</span> <span class="na">type=</span><span class="s">"text"</span><span class="nt">/&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"address_since"</span> <span class="na">type=</span><span class="s">"timestamp"</span><span class="nt">/&gt;</span>
            <span class="nt">&lt;column</span> <span class="na">name=</span><span class="s">"customer_id"</span> <span class="na">type=</span><span class="s">"bigint"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;/createTable&gt;</span>

        <span class="nt">&lt;addForeignKeyConstraint</span> <span class="na">baseTableName=</span><span class="s">"address"</span> <span class="na">baseColumnNames=</span><span class="s">"customer_id"</span> <span class="na">constraintName=</span><span class="s">"customer_x_addresses"</span>
                                 <span class="na">referencedTableName=</span><span class="s">"customer"</span> <span class="na">referencedColumnNames=</span><span class="s">"id"</span><span class="nt">/&gt;</span>

        <span class="nt">&lt;rollback&gt;</span>
            <span class="nt">&lt;dropForeignKeyConstraint</span> <span class="na">baseTableName=</span><span class="s">"address"</span> <span class="na">constraintName=</span><span class="s">"customer_x_addresses"</span><span class="nt">/&gt;</span>

            <span class="nt">&lt;dropTable</span> <span class="na">tableName=</span><span class="s">"address"</span><span class="nt">/&gt;</span>
        <span class="nt">&lt;/rollback&gt;</span>

<span class="nt">&lt;/changeSet&gt;</span></code></pre></figure>


También necesitamos adaptar nuestro código:


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="nd">@Entity</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"address"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Address</span> <span class="o">{</span>

    <span class="nd">@Id</span>
    <span class="nd">@GeneratedValue</span>
    <span class="kd">private</span> <span class="n">Long</span> <span class="n">id</span><span class="o">;</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"street_name"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">streetName</span><span class="o">;</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"street_number"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">;</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"postal_code"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">;</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"city"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">String</span> <span class="n">city</span><span class="o">;</span>
    <span class="nd">@Column</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"address_since"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">Date</span> <span class="n">addressSince</span><span class="o">;</span>

    <span class="kd">public</span> <span class="nf">Address</span><span class="o">()</span> <span class="o">{</span>
    <span class="o">}</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="n">Long</span> <span class="nf">id</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="n">id</span><span class="o">;</span>
    <span class="o">}</span>

    <span class="o">..</span>

<span class="o">}</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Customer</span> <span class="o">{</span>

    <span class="o">...</span>

    <span class="nd">@OneToMany</span><span class="o">(</span><span class="n">cascade</span> <span class="o">=</span> <span class="n">CascadeType</span><span class="o">.</span><span class="na">ALL</span><span class="o">)</span>
    <span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"customer_id"</span><span class="o">,</span> <span class="n">referencedColumnName</span> <span class="o">=</span> <span class="s">"id"</span><span class="o">)</span>
    <span class="kd">private</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">Address</span><span class="o">&gt;</span> <span class="n">addressHistory</span><span class="o">;</span>

    <span class="o">...</span>

<span class="o">}</span></code></pre></figure>


Como queremos que los cambios sean compatibles entre distintas versiones,
debemos aún mantener la antigua estructura conviviendo con la nueva al mismo
tiempo. Esto lo conseguimos gracias a la implementación que hemos hecho del
método updateAddress:


<figure class="highlight"><pre><code class="language-java" data-lang="java">     <span class="kd">public</span> <span class="kt">void</span> <span class="nf">updateAddress</span><span class="o">(</span><span class="n">String</span> <span class="n">streetName</span><span class="o">,</span> <span class="n">String</span> <span class="n">streetNumber</span><span class="o">,</span> <span class="n">String</span> <span class="n">postalCode</span><span class="o">,</span> <span class="n">String</span> <span class="n">city</span><span class="o">,</span> <span class="n">Date</span> <span class="n">addressChangeDate</span><span class="o">)</span> <span class="o">{</span>
         <span class="k">this</span><span class="o">.</span><span class="na">streetName</span> <span class="o">=</span> <span class="n">streetName</span><span class="o">;</span>
         <span class="k">this</span><span class="o">.</span><span class="na">streetNumber</span> <span class="o">=</span> <span class="n">streetNumber</span><span class="o">;</span>
         <span class="k">this</span><span class="o">.</span><span class="na">postalCode</span> <span class="o">=</span> <span class="n">postalCode</span><span class="o">;</span>
         <span class="k">this</span><span class="o">.</span><span class="na">city</span> <span class="o">=</span> <span class="n">city</span><span class="o">;</span>
         <span class="k">this</span><span class="o">.</span><span class="na">addressSince</span> <span class="o">=</span> <span class="n">addressChangeDate</span><span class="o">;</span>

         <span class="k">this</span><span class="o">.</span><span class="na">addressHistory</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="n">Address</span><span class="o">(</span><span class="n">streetName</span><span class="o">(),</span> <span class="n">streetNumber</span><span class="o">(),</span> <span class="n">postalCode</span><span class="o">(),</span> <span class="n">city</span><span class="o">(),</span> <span class="n">addressSince</span><span class="o">()));</span>
     <span class="o">}</span></code></pre></figure>


Esto significa que cuando una dirección cambia, dicho cambio se guarda en dos
sitios al mismo tiempo:
* En la nueva tabla de direcciones que guarda nuestro historial
* En los antiguos campos para direcciones que había en la tabla de clientes

¿Por qué hacemos esto? Porque es la forma en que garantizamos que dos versiones
de nuestra aplicación podrán acceder a la base de datos para consultar la
dirección de un cliente y que siempre podrán encontrar dicha información.

Imaginemos qué pasaría si no lo hiciéramos así. Supongamos que no tenemos
ninguna dirección almacenada para un cliente y recibimos una llamada al servicio
que actualiza los datos de un cliente proporcionando una dirección. Si esta
llamada fuera atendida por la versión nueva del sistema y únicamente almacenara
la dirección en la nueva tabla, ¿qué pasaría si la siguiente consulta sobre
dicho cliente fuera atendida por la versión antigua del sistema? Pues que
buscaría la dirección del cliente en los campos dentro de la tabla de clientes y
no encontraría nada. Por ello, la versión nueva del código debe preocuparse de
guardar las direcciones en los dos sitios a la vez.

Otro aspecto importante que debemos destacar es que el sistema empezará a
guardar las direcciones en la nueva tabla, pero seguirá utilizando como fuente
de datos primaria para la consulta de direcciones los campos que hay en la tabla
de clientes. Es decir, la nueva versión que estamos preparando guarda los datos
en el antiguo formato y en el nuevo, pero los devuelve utilizando el antiguo
método. Esta forma de hacer cambios corresponde a una técnica más general que
podemos aplicar cuando queremos sustituir un sistema (o subsistema) minimizando
riesgos. El procedimiento, de forma general, sería:
* Implantar el nuevo sistema que va a sustituir a uno ya existente
* Empezar a utilizar el nuevo sistema al mismo tiempo que seguimos usando el
antiguo. Usar el antiguo sistema como fuente primaria (el nuevo sistema se
mantiene en la sombra)
* Hacer el nuevo sistema nuestra fuente primaria. Seguimos utilizando el antiguo
sistema, siendo éste el que ahora está en la sombra.
* Cuando estamos seguros de que el nuevo sistema funciona correctamente y ya no
necesitamos el antiguo, prescindimos de él y eliminamos todas las referencias al
mismo.

Podemos hacer la prueba de que todo funciona correctamente, manteniendo la
compatilibidad entre versiones parando uno de los servidores, actualizando el
esquema de nuestra base de datos y arrancando el servidor de nuevo. Las
peticiones que se hagan a servidor con el nuevo código que actualicen la
dirección de un cliente guardarán los nuevos datos tanto en las columnas de la
tabla de clientes como en la nueva tabla de direcciones que hemos creado. Las
que se dirijan al servidor con el antiguo código guardarán las nuevas
direcciones únicamente en los campos de la tabla de clientes. Ambos servidores
devolverán la información más actual que exista de la dirección de un cliente
utilizando los campos de la tabla de clientes.

Después de esta comprobación, podemos actualizar el segundo servidor para
completar el despliegue de la nueva versión. Como resultado, ahora nuestro
sistema siempre guarda las direcciones nuevas en la tabla de direcciones,
manteniendo un registros histórico de las direcciones utilizadas por un cliente
(también almacena, por motivos de compatibilidad, dicha información en los
antiguos campos existentes en la tabla de clientes).

Todo esto está muy bien, pero todavía existen direcciones que únicamente están
almacenadas en los campos de la tabla de clientes (se crearon antes de que la
nueva versión se pusiera en producción y no han sido actualizados desde
entonces). Éste es el motivo por el que debemos seguir tomando los campos en la
tabla de clientes como la fuente primaria cuando atendemos una consulta de
datos sobre un cliente. Ahora debemos migrar la información acerca de
direcciones a la nueva tabla. Dependiendo de con cuánta frecuencia se hayan
actualizado las direcciones de clientes desde que la nueva versión que usa la
nueva tabla de direcciones, el número de direcciones almacenadas únicamente en
el antiguo formato será mayor o menor y, por lo tanto, el proceso de migración
tardará más o menos. Nuestro objetivo, en todo caso, es que dicho proceso se
haga, como siempre de una forma que no implique dejar de prestar nuestro servicio.

Podemos distinguir dos casos:
* clientes con direcciones almacenadas únicamente en los antiguos campos
exisistentes en la tabla de clientes
* clientes con direcciones almacenadas tanto en los campos de la tabla de
clientes como en la nueva tabla de direccciones

En el primer caso, basta con crear una nueva entrada en la tabla de direcciones
con la misma información que teníamos en los campos de la tabla de clientes.
Esto se puede hacer fácilmente con el siguiente script SQL:


<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">WITH</span> <span class="n">caddresses_old_format</span> <span class="k">AS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="k">c</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="n">customer</span> <span class="k">c</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="n">address</span> <span class="n">a</span> <span class="k">ON</span> <span class="n">a</span><span class="p">.</span><span class="n">customer_id</span> <span class="o">=</span> <span class="k">c</span><span class="p">.</span><span class="n">id</span>
<span class="k">WHERE</span> <span class="p">(</span><span class="k">c</span><span class="p">.</span><span class="n">street_name</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="k">c</span><span class="p">.</span><span class="n">street_number</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="k">c</span><span class="p">.</span><span class="n">postal_code</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="k">c</span><span class="p">.</span><span class="n">city</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">)</span>
   <span class="k">AND</span> <span class="n">a</span><span class="p">.</span><span class="n">id</span> <span class="k">IS</span> <span class="k">NULL</span><span class="p">)</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">address</span> <span class="p">(</span>
   <span class="n">id</span><span class="p">,</span>
   <span class="n">street_name</span><span class="p">,</span>
   <span class="n">street_number</span><span class="p">,</span>
   <span class="n">postal_code</span><span class="p">,</span>
   <span class="n">city</span><span class="p">,</span>
   <span class="n">address_since</span><span class="p">,</span>
   <span class="n">customer_id</span><span class="p">)</span>
<span class="k">SELECT</span>
   <span class="n">nextval</span><span class="p">(</span><span class="s1">'address_id_seq'</span><span class="p">),</span>
   <span class="n">caddresses_old_format</span><span class="p">.</span><span class="n">street_name</span><span class="p">,</span>
   <span class="n">caddresses_old_format</span><span class="p">.</span><span class="n">street_number</span><span class="p">,</span>
   <span class="n">caddresses_old_format</span><span class="p">.</span><span class="n">postal_code</span><span class="p">,</span>
   <span class="n">caddresses_old_format</span><span class="p">.</span><span class="n">city</span><span class="p">,</span>
   <span class="n">caddresses_old_format</span><span class="p">.</span><span class="n">address_since</span><span class="p">,</span>
   <span class="n">caddresses_old_format</span><span class="p">.</span><span class="n">id</span>
<span class="k">FROM</span> <span class="n">caddresses_old_format</span><span class="p">;</span></code></pre></figure>


¿Qué problema podemos tener con los clientes que ya tengan datos en la nueva
tabla de direcciones? En principio, podría parecer que, si esto es así, dicha
información tiene que ser la más actual, ya que ha sido creada por una versión
del sistema más nueva. Sin embargo, debemos recordar que, durante un tiempo,
hemos mantenido dos versiones de nuestro sistema en ejecución al mismo tiempo.
Por lo tanto, es posible (y, dependiendo del tráfico que recibamos, puede que
incluso probable) que haya habido casos como el siguiente:
* un cliente no tiene ninguna dirección
* recibimos una petición para actualizar su dirección
* dicha petición es atendida por una versión actualizada del sistema
* la dirección se guarda en la nueva tabla de direcciones así como en los campos
existentes en la tabla de clientes
* recibimos una nueva petición para actualizar la dirección del mismo cliente
* dicha petición es atendida por la versión antigua del sistema
* la dirección se guarda únicamente en los campos existentes en la tabla de
clientes
* la dirección del cliente no vuelve a actualizarse más

Como vemos, es posible que haya direcciones en los campos de la tabla de
clientes que sean más actuales que los datos existentes en la tabla de
direcciones. ¿Cómo podemos migrar los datos en estos casos? En este caso es
donde se muestra realmente útil el haber introducido, con nuestro primer cambio,
el campo con la fecha de actualización de la dirección en la tabla de clientes.
Gracias a que tenemos las fechas en las que una dirección ha sido actualizada en
ambas tablas (clientes y direcciones), es sencillo determinar cuál de ellas es
la más actual en caso de que sean distintas. Basta comparar la fecha de cambio
de dirección en la tabla de clientes con la fecha de cambio de dirección de la
más reciente entrada en la tabla de direcciones:


<figure class="highlight"><pre><code class="language-sql" data-lang="sql"><span class="k">WITH</span> <span class="n">caddresses_not_updated</span> <span class="k">AS</span> <span class="p">(</span><span class="k">SELECT</span> <span class="k">c</span><span class="p">.</span><span class="o">*</span> <span class="k">FROM</span> <span class="n">customer</span> <span class="k">c</span> <span class="k">LEFT</span> <span class="k">JOIN</span> <span class="n">address</span> <span class="n">a</span> <span class="k">ON</span> <span class="n">a</span><span class="p">.</span><span class="n">customer_id</span> <span class="o">=</span> <span class="k">c</span><span class="p">.</span><span class="n">id</span>
<span class="k">WHERE</span> <span class="p">(</span><span class="k">c</span><span class="p">.</span><span class="n">street_name</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="k">c</span><span class="p">.</span><span class="n">street_number</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="k">c</span><span class="p">.</span><span class="n">postal_code</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">OR</span> <span class="k">c</span><span class="p">.</span><span class="n">city</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">)</span>
   <span class="k">AND</span> <span class="n">a</span><span class="p">.</span><span class="n">id</span> <span class="k">IS</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">AND</span> <span class="k">NOT</span> <span class="k">exists</span><span class="p">(</span><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">address</span> <span class="n">a2</span> <span class="k">WHERE</span> <span class="n">a2</span><span class="p">.</span><span class="n">customer_id</span> <span class="o">=</span> <span class="k">c</span><span class="p">.</span><span class="n">id</span> <span class="k">AND</span> <span class="n">a2</span><span class="p">.</span><span class="n">address_since</span> <span class="o">&gt;</span> <span class="n">a</span><span class="p">.</span><span class="n">address_since</span><span class="p">)</span>
   <span class="k">AND</span> <span class="k">c</span><span class="p">.</span><span class="n">address_since</span> <span class="o">&gt;</span> <span class="n">a</span><span class="p">.</span><span class="n">address_since</span><span class="p">)</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">address</span> <span class="p">(</span>
   <span class="n">id</span><span class="p">,</span>
   <span class="n">street_name</span><span class="p">,</span>
   <span class="n">street_number</span><span class="p">,</span>
   <span class="n">postal_code</span><span class="p">,</span>
   <span class="n">city</span><span class="p">,</span>
   <span class="n">address_since</span><span class="p">,</span>
   <span class="n">customer_id</span><span class="p">)</span>
<span class="k">SELECT</span>
   <span class="n">nextval</span><span class="p">(</span><span class="s1">'address_id_seq'</span><span class="p">),</span>
   <span class="n">caddresses_not_updated</span><span class="p">.</span><span class="n">street_name</span><span class="p">,</span>
   <span class="n">caddresses_not_updated</span><span class="p">.</span><span class="n">street_number</span><span class="p">,</span>
   <span class="n">caddresses_not_updated</span><span class="p">.</span><span class="n">postal_code</span><span class="p">,</span>
   <span class="n">caddresses_not_updated</span><span class="p">.</span><span class="n">city</span><span class="p">,</span>
   <span class="n">caddresses_not_updated</span><span class="p">.</span><span class="n">address_since</span><span class="p">,</span>
   <span class="n">caddresses_not_updated</span><span class="p">.</span><span class="n">id</span>
<span class="k">FROM</span> <span class="n">caddresses_not_updated</span><span class="p">;</span></code></pre></figure>


Ahora tenemos todos nuestros servidores ejecutando la última versión, que guarda
as direcciones en la tabla de direcciones, y hemos migrado los datos de
direcciones a la tabla address, incluso aquellos que, estando únicamente en la
tabla customer, pudieran ser más actuales. Este es el momento en el que podemos
tomar la información de la tabla de direcciones como fuente primaria cuando
atendemos consultas de datos de clientes.


<figure class="highlight"><pre><code class="language-java" data-lang="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Customer</span> <span class="o">{</span>

    <span class="o">...</span>

    <span class="kd">public</span> <span class="n">Address</span> <span class="nf">currentAddress</span><span class="o">()</span> <span class="o">{</span>
        <span class="k">return</span> <span class="nf">addressHistory</span><span class="o">().</span><span class="na">stream</span><span class="o">().</span><span class="na">sorted</span><span class="o">(</span><span class="n">comparing</span><span class="o">(</span><span class="nl">Address:</span><span class="o">:</span><span class="n">addressSince</span><span class="o">).</span><span class="na">reversed</span><span class="o">()).</span><span class="na">findFirst</span><span class="o">().</span><span class="na">get</span><span class="o">();</span>
    <span class="o">}</span>

    <span class="o">...</span>

<span class="o">}</span></code></pre></figure>


Podemos ver cómo ahora devolvemos las direcciones utilizando únicamente la
información de la tabla de direcciones. Aún así, seguimos almacenando las
direcciones tanto en la tabla de direcciones como en las columnas de la tabla de
clientes. Podemos decir que ahora es el antiguo sistema que almacenaba las
direcciones en la tabla de clientes el que ha pasado a estar en la sombra (aunque
sigue funcinando). No es una mala idea mantener el sistema antiguo en la
sombra durante un tiempo antes de eliminarlo definitivamente. Algunas ventajas
de hacerlo así son:
* podemos comparar los resultados con ambos sistemas (si vemos discrepancias
  podría ser síntoma de que algo no funciona bien)
* si algo sale muy mal con el nuevo sistema, resulta fácil volver a utilizar el
sistema antiguo
* puede haber otros equipos utilizando el sistema antiguo que necesiten algún
tiempo hasta que se adapten al nuevo (de esta forma, tienen más tiempo para
llevar a cabo su migración particular)

Por supuesto, en nuestro caso, el precio que tiene mantener los datos de
direcciones tanto en la tabla de clientes como en la nueva tabla de direcciones
es que nuestro código será más complejo, además de la duplicidad innecesaria de
datos en la base de datos. Se trata, en todo caso, de un estado temporal que nos
proporciona el beneficio de poder construir la funcionalidad del historial de
direcciones, que requería de una refactorización grande en nuestro código y
modelo de datos, sin haber dejado de prestar el servicio ni un solo minuto.

Ahora sí podemos dejar de usar las columnas de la tabla de clientes. Con objeto
de hacer el cambio compatible entre distintas versiones para poder tener siempre,
al menos, un servidor en ejecución, empezamos eliminando las referencias a dichas
columnas en nuestro código:

highlight java %}
public class Customer {

    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "customer_id", referencedColumnName = "id")
    private List<Address> addressHistory;

    public Customer() {
    }

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.addressHistory = new ArrayList<>();
    }

    ...

}

Y después, cuando este cambio haya sido implantado en todos los servidores, podemos definitivamente eliminar las columnas relativas a direcciones de la tabla de clientes.

<changeSet id="customer-005" author="joragupra">

        <comment>Delete columns for address information from customer table.</comment>

        <dropColumn tableName="customer" columnName="street_name"/>
        <dropColumn tableName="customer" columnName="street_number"/>
        <dropColumn tableName="customer" columnName="postal_code"/>
        <dropColumn tableName="customer" columnName="city"/>
        <dropColumn tableName="customer" columnName="address_since"/>

        <rollback>
            <addColumn tableName="customer">
                <column name="street_name" type="text">
                    <constraints nullable="true"/>
                </column>
            </addColumn>
            <addColumn tableName="customer">
                <column name="street_number" type="text">
                    <constraints nullable="true"/>
                </column>
            </addColumn>
            <addColumn tableName="customer">
                <column name="postal_code" type="text">
                    <constraints nullable="true"/>
                </column>
            </addColumn>
            <addColumn tableName="customer">
                <column name="city" type="text">
                    <constraints nullable="true"/>
                </column>
            </addColumn>
            <addColumn tableName="customer">
                <column name="address_since" type="timestamp">
                    <constraints nullable="true"/>
                </column>
            </addColumn>
        </rollback>

</changeSet>

Ahora sí los cambios necesarios para mantener un historial de direcciones han sido completados. Hemos visto cómo podemos conseguir hacer cambios grandes en nuestro código sin necesidad de parar nuestro servicio, lo que ayuda a conseguir una alta disponibilidad.

Reflexión final

¿Merece la pena dar tantas vueltas para hacer un cambio que podríamos haber implementado en un único paso? La respuesta, como casi siempre, es “depende”. Se trata de valorar qué es más importante para nosotros en cada momento. Posiblemente si estamos trabajando con un sistema todavía inmaduro en el que experimentamos constantemente con nuevas funcionaliades que requieren cambios muy grandes, quizá lo que más nos interese es que los cambios estén completamente implementados y en producción lo antes posible, aunque eso signifique que el sistema esté indisponible por algún tiempo cada vez que implantamos un cambio nuevo. Por otro lado, si no queremos que nuestros usuarios experimienten ninguna caída del servicio, o no nos los podemos permitir (por ejemplo, si es un API pública que ofrecemos a terceros y tiene que estar siempre disponible), posiblemente sea una buena idea tomar el camino no tan directo pero siempre compatible que nos garantiza que podemos actualizar nuestros servidores de forma escalonada.

Referencias

Puedes encontrar el código utilizado como ejemplo en este artículo aquí.