Java tutorial
// Copyright 2012,2013 Vaughn Vernon // // 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 com.saasovation.common.port.adapter.persistence.eventsourcing.mysql; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import com.saasovation.common.domain.model.DomainEvent; import com.saasovation.common.event.EventSerializer; import com.saasovation.common.event.sourcing.DispatchableDomainEvent; import com.saasovation.common.event.sourcing.EventNotifiable; import com.saasovation.common.event.sourcing.EventStore; import com.saasovation.common.event.sourcing.EventStoreAppendException; import com.saasovation.common.event.sourcing.EventStoreException; import com.saasovation.common.event.sourcing.EventStream; import com.saasovation.common.event.sourcing.EventStreamId; import com.saasovation.common.port.adapter.persistence.eventsourcing.DefaultEventStream; public class MySQLJDBCEventStore implements EventStore, ApplicationContextAware { private static MySQLJDBCEventStore instance; private DataSource collaborationDataSource; private EventNotifiable eventNotifiable; private EventSerializer serializer; public synchronized static MySQLJDBCEventStore instance() { return instance; } public MySQLJDBCEventStore(DataSource aDataSource) { super(); this.setCollaborationDataSource(aDataSource); this.setSerializer(EventSerializer.instance()); } @Override public void appendWith(EventStreamId aStartingIdentity, List<DomainEvent> anEvents) { // tbl_es_event_store must have a composite primary key // consisting of {stream_name}:{streamVersion} so that // appending a stale version will fail the pk constraint Connection connection = this.connection(); try { int index = 0; for (DomainEvent event : anEvents) { this.appendEventStore(connection, aStartingIdentity, index++, event); } connection.commit(); this.notifyDispatchableEvents(); } catch (Throwable t1) { try { this.connection().rollback(); } catch (Throwable t2) { // ignore } throw new EventStoreAppendException("Could not append to event store because: " + t1.getMessage(), t1); } finally { try { connection.close(); } catch (SQLException e) { // ignore } } } @Override public void close() { // no-op } @Override public List<DispatchableDomainEvent> eventsSince(long aLastReceivedEvent) { Connection connection = this.connection(); ResultSet result = null; try { PreparedStatement statement = connection .prepareStatement("SELECT event_id, event_body, event_type FROM tbl_es_event_store " + "WHERE event_id > ? " + "ORDER BY event_id"); statement.setLong(1, aLastReceivedEvent); result = statement.executeQuery(); List<DispatchableDomainEvent> sequence = this.buildEventSequence(result); connection.commit(); return sequence; } catch (Throwable t) { throw new EventStoreException( "Cannot query event for sequence since: " + aLastReceivedEvent + " because: " + t.getMessage(), t); } finally { if (result != null) { try { result.close(); } catch (SQLException e) { // ignore } } try { connection.close(); } catch (SQLException e) { // ignore } } } @Override public EventStream eventStreamSince(EventStreamId anIdentity) { Connection connection = this.connection(); ResultSet result = null; try { PreparedStatement statement = connection .prepareStatement("SELECT stream_version, event_type, event_body FROM tbl_es_event_store " + "WHERE stream_name = ? AND stream_version >= ? " + "ORDER BY stream_version"); statement.setString(1, anIdentity.streamName()); statement.setInt(2, anIdentity.streamVersion()); result = statement.executeQuery(); EventStream eventStream = this.buildEventStream(result); if (eventStream.version() == 0) { throw new EventStoreException("There is no such event stream: " + anIdentity.streamName() + " : " + anIdentity.streamVersion()); } connection.commit(); return eventStream; } catch (Throwable t) { throw new EventStoreException("Cannot query event stream for: " + anIdentity.streamName() + " since version: " + anIdentity.streamVersion() + " because: " + t.getMessage(), t); } finally { if (result != null) { try { result.close(); } catch (SQLException e) { // ignore } } try { connection.close(); } catch (SQLException e) { // ignore } } } @Override public EventStream fullEventStreamFor(EventStreamId anIdentity) { Connection connection = this.connection(); ResultSet result = null; try { PreparedStatement statement = connection .prepareStatement("SELECT stream_version, event_type, event_body FROM tbl_es_event_store " + "WHERE stream_name = ? " + "ORDER BY stream_version"); statement.setString(1, anIdentity.streamName()); result = statement.executeQuery(); connection.commit(); return this.buildEventStream(result); } catch (Throwable t) { throw new EventStoreException("Cannot query full event stream for: " + anIdentity.streamName() + " because: " + t.getMessage(), t); } finally { if (result != null) { try { result.close(); } catch (SQLException e) { // ignore } } try { connection.close(); } catch (SQLException e) { // ignore } } } @Override public void purge() { Connection connection = this.connection(); try { connection.createStatement().execute("delete from tbl_es_event_store"); connection.commit(); } catch (Throwable t) { throw new EventStoreException("Problem purging event store because: " + t.getMessage(), t); } finally { try { connection.close(); } catch (SQLException e) { // ignore } } } @Override public void registerEventNotifiable(EventNotifiable anEventNotifiable) { this.eventNotifiable = anEventNotifiable; } private void appendEventStore(Connection aConnection, EventStreamId anIdentity, int anIndex, DomainEvent aDomainEvent) throws Exception { PreparedStatement statement = aConnection .prepareStatement("INSERT INTO tbl_es_event_store VALUES(?, ?, ?, ?, ?)"); statement.setLong(1, 0); statement.setString(2, this.serializer().serialize(aDomainEvent)); statement.setString(3, aDomainEvent.getClass().getName()); statement.setString(4, anIdentity.streamName()); statement.setInt(5, anIdentity.streamVersion() + anIndex); statement.executeUpdate(); } @SuppressWarnings("unchecked") private List<DispatchableDomainEvent> buildEventSequence(ResultSet aResultSet) throws Exception { List<DispatchableDomainEvent> events = new ArrayList<DispatchableDomainEvent>(); while (aResultSet.next()) { long eventId = aResultSet.getLong("event_id"); String eventClassName = aResultSet.getString("event_type"); String eventBody = aResultSet.getString("event_body"); Class<DomainEvent> eventClass = (Class<DomainEvent>) Class.forName(eventClassName); DomainEvent domainEvent = this.serializer().deserialize(eventBody, eventClass); events.add(new DispatchableDomainEvent(eventId, domainEvent)); } return events; } @SuppressWarnings("unchecked") private EventStream buildEventStream(ResultSet aResultSet) throws Exception { List<DomainEvent> events = new ArrayList<DomainEvent>(); int version = 0; while (aResultSet.next()) { version = aResultSet.getInt("stream_version"); String eventClassName = aResultSet.getString("event_type"); String eventBody = aResultSet.getString("event_body"); Class<DomainEvent> eventClass = (Class<DomainEvent>) Class.forName(eventClassName); DomainEvent domainEvent = this.serializer().deserialize(eventBody, eventClass); events.add(domainEvent); } return new DefaultEventStream(events, version); } private DataSource collaborationDataSource() { return this.collaborationDataSource; } private void setCollaborationDataSource(DataSource aDataSource) { this.collaborationDataSource = aDataSource; } private Connection connection() { Connection connection = null; try { connection = this.collaborationDataSource().getConnection(); } catch (SQLException e) { throw new IllegalStateException("Cannot acquire database connection."); } return connection; } private EventNotifiable eventNotifiable() { return this.eventNotifiable; } private void notifyDispatchableEvents() { EventNotifiable eventNotifiable = this.eventNotifiable(); if (eventNotifiable != null) { this.eventNotifiable().notifyDispatchableEvents(); } } private EventSerializer serializer() { return this.serializer; } private void setSerializer(EventSerializer aSerializer) { this.serializer = aSerializer; } @Override public synchronized void setApplicationContext(ApplicationContext anApplicationContext) throws BeansException { instance = anApplicationContext.getBean(MySQLJDBCEventStore.class); } }