1

Moarr work!

This commit is contained in:
2018-05-03 14:07:02 +03:00
parent 330d53df94
commit ca6da1dbe9
22 changed files with 583 additions and 238 deletions

View File

@@ -21,6 +21,7 @@ repositories {
dependencies { dependencies {
compile ratpack.dependency('h2') compile ratpack.dependency('h2')
compile ratpack.dependency('jdbc-tx') compile ratpack.dependency('jdbc-tx')
compile ratpack.dependency('session')
compile 'org.flywaydb:flyway-core:5.0.7' compile 'org.flywaydb:flyway-core:5.0.7'
runtime 'org.slf4j:slf4j-simple:1.7.25' runtime 'org.slf4j:slf4j-simple:1.7.25'

View File

@@ -1,62 +0,0 @@
package com.devsoap.dbt
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
class BlockTransaction implements Serializable {
String id = UUID.randomUUID().toString()
List<Query> queries = []
boolean completed = false
boolean executed = false
boolean rolledback = false
/**
* Executes a query in the transaction
*
* @param query
* the query to execute
* @return
* the result of the query
*/
void execute(String query) {
queries << new Query(queries.empty? null : queries.last(), query)
}
/**
* End the current transaction
*
* @return
*/
void end() {
completed = true
}
// A block in the chain
static final class Query implements Serializable {
String data
String hash
long timeStamp
Object result
Query() {
// For serialization
}
Query(Query previous, String data) {
this.data = data
timeStamp = new Date().getTime()
hash = generateHash(previous?.hash)
}
private final String generateHash(String previousHash) {
def digest = MessageDigest.getInstance('SHA-256')
def hash = digest.digest("${previousHash?:''}$timeStamp$data".getBytes(StandardCharsets.UTF_8))
hash.encodeHex().toString()
}
}
}

View File

@@ -0,0 +1,62 @@
package com.devsoap.dbt
import com.devsoap.dbt.actions.ExecutorChainAction
import com.devsoap.dbt.actions.LedgerChainAction
import com.devsoap.dbt.config.DBTConfig
import com.devsoap.dbt.data.LedgerData
import com.devsoap.dbt.handlers.ExecutorHandler
import com.devsoap.dbt.handlers.LedgerGetTransactionHandler
import com.devsoap.dbt.handlers.LedgerListTransactionsHandler
import com.devsoap.dbt.handlers.LedgerUpdateTransactionHandler
import com.devsoap.dbt.services.LedgerService
import com.devsoap.dbt.services.TransactionManagerService
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.inject.multibindings.Multibinder
import groovy.util.logging.Slf4j
import ratpack.guice.ConfigurableModule
import ratpack.handling.HandlerDecorator
import ratpack.server.ServerConfig
import ratpack.session.Session
@Slf4j
class DBTModule extends ConfigurableModule<DBTConfig> {
@Override
protected void configure() {
bind(ObjectMapper)
bind(LedgerChainAction)
bind(LedgerGetTransactionHandler)
bind(LedgerListTransactionsHandler)
bind(LedgerUpdateTransactionHandler)
bind(ExecutorChainAction)
bind(ExecutorHandler)
bind(LedgerData)
bind(LedgerService)
bind(TransactionManagerService)
Multibinder.newSetBinder(binder(), HandlerDecorator).addBinding()
.toInstance(HandlerDecorator.prependHandlers(LedgerChainAction))
Multibinder.newSetBinder(binder(), HandlerDecorator).addBinding()
.toInstance(HandlerDecorator.prependHandlers(ExecutorChainAction))
}
@Override
protected DBTConfig createConfig(ServerConfig serverConfig) {
(DBTConfig) serverConfig.getAsConfigObject('/dbt', DBTConfig)?.getObject() ?: super.createConfig(serverConfig)
}
@Override
protected void defaultConfig(ServerConfig serverConfig, DBTConfig config) {
if(!config.executor.remoteUrl) {
config.executor.remoteUrl = "http://localhost:${serverConfig.port}/${config.executor.path}"
}
if(!config.ledger.remoteUrl) {
config.executor.remoteUrl = "http://localhost:${serverConfig.port}/${config.ledger.path}"
}
}
}

View File

@@ -0,0 +1,24 @@
package com.devsoap.dbt.actions
import com.devsoap.dbt.config.DBTConfig
import com.devsoap.dbt.handlers.ExecutorHandler
import com.google.inject.Inject
import groovy.util.logging.Slf4j
import ratpack.groovy.handling.GroovyChainAction
@Slf4j
class ExecutorChainAction extends GroovyChainAction {
private final String executorPath
@Inject
ExecutorChainAction(DBTConfig config) {
executorPath = config.executor.path
}
@Override
void execute() throws Exception {
log.info("Registering executor at $executorPath")
path(executorPath, ExecutorHandler)
}
}

View File

@@ -0,0 +1,31 @@
package com.devsoap.dbt.actions
import com.devsoap.dbt.config.DBTConfig
import com.devsoap.dbt.handlers.LedgerGetTransactionHandler
import com.devsoap.dbt.handlers.LedgerListTransactionsHandler
import com.devsoap.dbt.handlers.LedgerUpdateTransactionHandler
import com.google.inject.Inject
import groovy.util.logging.Slf4j
import ratpack.groovy.handling.GroovyChainAction
import ratpack.handling.Handlers
@Slf4j
class LedgerChainAction extends GroovyChainAction {
private final String ledgerPath
@Inject
LedgerChainAction(DBTConfig config) {
ledgerPath = config.ledger.path
}
@Override
void execute() throws Exception {
log.info("Registering ledger at $ledgerPath")
path(ledgerPath, Handlers.chain(
registry.get(LedgerGetTransactionHandler),
registry.get(LedgerUpdateTransactionHandler),
registry.get(LedgerListTransactionsHandler)
))
}
}

View File

@@ -2,4 +2,5 @@ package com.devsoap.dbt.config
class DBTConfig { class DBTConfig {
LedgerConfig ledger = new LedgerConfig() LedgerConfig ledger = new LedgerConfig()
ExecutorConfig executor = new ExecutorConfig()
} }

View File

@@ -0,0 +1,6 @@
package com.devsoap.dbt.config
class ExecutorConfig {
String path = 'executor'
String remoteUrl
}

View File

@@ -1,5 +1,6 @@
package com.devsoap.dbt.config package com.devsoap.dbt.config
class LedgerConfig { class LedgerConfig {
String url = 'http://localhost:5050/ledger' String path = 'ledger'
String remoteUrl
} }

View File

@@ -0,0 +1,82 @@
package com.devsoap.dbt.data
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.databind.JsonNode
import groovy.transform.ToString
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
@ToString
class BlockTransaction implements Serializable {
String id = UUID.randomUUID().toString().replace('-','')
List<Query> queries = []
boolean completed = false
boolean executed = false
boolean rolledback = false
/**
* Executes a query in the transaction
*
* @param query
* the query to execute
* @return
* the result of the query
*/
void execute(String query) {
if(queries.isEmpty()){
queries << new Query(this, query)
} else {
queries << new Query(queries.last(), query)
}
}
/**
* End the current transaction
*
* @return
*/
void end() {
completed = true
}
// A block in the chain
@ToString
static final class Query implements Serializable {
String query
String id
String parent
long timeStamp
Map<String, List> result
Query() {
// For serialization
}
Query(Query previous, String query) {
this.query = query
timeStamp = new Date().getTime()
parent = previous.id
id = generateHash()
}
Query(BlockTransaction transaction, String query) {
this.query = query
timeStamp = new Date().getTime()
parent = transaction.id
id = generateHash()
}
final String generateHash() {
def digest = MessageDigest.getInstance('SHA-256')
def hash = digest.digest("${parent?:''}$timeStamp$query".getBytes(StandardCharsets.UTF_8))
hash.encodeHex().toString()
}
}
}

View File

@@ -0,0 +1,6 @@
package com.devsoap.dbt.data
class LedgerData implements Serializable {
List<BlockTransaction> transactions = new ArrayList<>()
}

View File

@@ -1,10 +1,10 @@
package com.devsoap.dbt.handlers package com.devsoap.dbt.handlers
import com.devsoap.dbt.BlockTransaction import com.devsoap.dbt.data.BlockTransaction
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode import com.fasterxml.jackson.databind.node.ArrayNode
import groovy.util.logging.Log import groovy.util.logging.Slf4j
import ratpack.exec.Promise import ratpack.exec.Promise
import ratpack.handling.Context import ratpack.handling.Context
import ratpack.handling.Handler import ratpack.handling.Handler
@@ -14,11 +14,9 @@ import ratpack.jdbctx.Transaction
import javax.sql.DataSource import javax.sql.DataSource
import java.sql.ResultSet import java.sql.ResultSet
@Log @Slf4j
class ExecutorHandler implements Handler { class ExecutorHandler implements Handler {
static final String PATH = 'executor'
@Override @Override
void handle(Context ctx) throws Exception { void handle(Context ctx) throws Exception {
ctx.request.body.then { body -> ctx.request.body.then { body ->
@@ -27,7 +25,7 @@ class ExecutorHandler implements Handler {
def transaction = mapper.readValue(body.text, BlockTransaction) def transaction = mapper.readValue(body.text, BlockTransaction)
if(!validateChain(transaction)) { if(!validateChain(transaction)) {
ctx.response.status = Status.of(400, 'Transaction chain invalid') ctx.response.status(Status.of(400, 'Transaction chain invalid'))
return return
} }
@@ -38,25 +36,38 @@ class ExecutorHandler implements Handler {
} }
} }
boolean validateChain(BlockTransaction transaction) { private static boolean validateChain(BlockTransaction transaction) {
//FIXME if(transaction.queries[0].parent != transaction.id) {
return false
}
for(int i=1; i<transaction.queries.size(); i++) {
def query = transaction.queries[i]
def prev = transaction.queries[i-1]
if(query.id != query.generateHash()) {
return false
}
if(query.parent != prev.generateHash()) {
return false
}
}
true true
} }
Promise<BlockTransaction> executeCommands(DataSource ds, ObjectMapper mapper, BlockTransaction transaction) { private static Promise<BlockTransaction> executeCommands(DataSource ds, ObjectMapper mapper, BlockTransaction transaction) {
def txDs = Transaction.dataSource(ds) def txDs = Transaction.dataSource(ds)
def tx = Transaction.create { ds.connection } def tx = Transaction.create { ds.connection }
tx.wrap { tx.wrap {
Promise.sync { Promise.sync {
transaction.queries.each { block -> transaction.queries.each { block ->
log.info "Executing $block.data ..." log.info "Executing $block.query ..."
if(block.data.toLowerCase().startsWith("select")){ if(block.query.toLowerCase().startsWith("select")){
log.info('Saving result from Select query')
def result = txDs.connection def result = txDs.connection
.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE) .createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)
.executeQuery(block.data) .executeQuery(block.query)
block.result = toJson(mapper, result) block.result = toMap(result)
} else { } else {
txDs.connection.createStatement().execute(block.data) txDs.connection.createStatement().execute(block.query)
} }
} }
transaction transaction
@@ -64,30 +75,26 @@ class ExecutorHandler implements Handler {
} }
} }
private static JsonNode toJson(ObjectMapper mapper, ResultSet resultSet) { private static Map toMap(ResultSet resultSet) {
def json = mapper.createObjectNode() def map = [:]
if(resultSet.last()) { if(resultSet.last()) {
int rows = resultSet.row
log.info("Converting $rows rows to json")
resultSet.beforeFirst() resultSet.beforeFirst()
resultSet.metaData.columnCount.times { column -> resultSet.metaData.columnCount.times { column ->
def columnIndex = column + 1 def columnIndex = column + 1
def columnName = resultSet.metaData.getColumnName(columnIndex) def columnName = resultSet.metaData.getColumnName(columnIndex)
ArrayNode columnValue = json.get(columnName)
if(!columnValue) { def columnValues = map[columnName] as List
columnValue = mapper.createArrayNode() if(columnValues == null) {
json.set(columnName, columnValue) map[columnName] = columnValues = []
} }
resultSet.beforeFirst() resultSet.beforeFirst()
while(resultSet.next()) { while(resultSet.next()) {
columnValue.addPOJO(resultSet.getObject(columnIndex)) columnValues << resultSet.getObject(columnIndex)
} }
} }
} }
//mapper.writeValueAsString(json) map
mapper.valueToTree(json)
} }
} }

View File

@@ -0,0 +1,34 @@
package com.devsoap.dbt.handlers
import com.devsoap.dbt.services.LedgerService
import groovy.util.logging.Log
import groovy.util.logging.Slf4j
import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.http.HttpMethod
import ratpack.jackson.Jackson
import ratpack.session.Session
@Slf4j
class LedgerGetTransactionHandler implements Handler {
@Override
void handle(Context ctx) throws Exception {
ctx.with {
if(request.method == HttpMethod.GET && header('X-Transaction-Id').present) {
def id = request.headers['X-Transaction-Id'].toString()
def ledgerService = ctx.get(LedgerService)
def session = ctx.get(Session)
ledgerService.fetchTransaction(session, id).then {
if(it.present) {
render(Jackson.json(it.get()))
} else {
notFound()
}
}
} else {
next()
}
}
}
}

View File

@@ -1,51 +0,0 @@
package com.devsoap.dbt.handlers
import com.devsoap.dbt.BlockTransaction
import com.devsoap.dbt.services.LedgerService
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.util.logging.Log
import ratpack.handling.Context
import ratpack.handling.Handler
@Log
class LedgerHandler implements Handler {
static final String PATH = 'ledger'
@Override
void handle(Context ctx) {
def ledgerService = ctx.get(LedgerService)
ctx.byMethod {
delegate = it
get({
def transaction = ledgerService.fetchTransaction(ctx.request.headers['X-Transaction-Id'].toString())
def mapper = ctx.get(ObjectMapper)
ctx.response.send(mapper.writeValueAsString(transaction))
} as Handler)
post({
ctx.request.body.then { body ->
def mapper = ctx.get(ObjectMapper)
def transaction = mapper.readValue(body.text, BlockTransaction)
def existingTransaction = ledgerService.fetchTransaction(transaction.id)
if(existingTransaction) {
ledgerService.updateTransaction(transaction)
} else {
log.info("Creating new transaction")
ledgerService.newTransaction(transaction)
}
if(transaction.completed){
log.info("Sending transaction $transaction.id to executor")
ctx.redirect(ExecutorHandler.PATH)
} else {
ctx.response.send(mapper.writeValueAsString(transaction))
}
}
} as Handler)
}
}
}

View File

@@ -0,0 +1,29 @@
package com.devsoap.dbt.handlers
import com.devsoap.dbt.services.LedgerService
import groovy.util.logging.Slf4j
import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.http.HttpMethod
import ratpack.jackson.Jackson
import ratpack.session.Session
@Slf4j
class LedgerListTransactionsHandler implements Handler {
@Override
void handle(Context ctx) throws Exception {
ctx.with {
if(request.method == HttpMethod.GET && !header('X-Transaction-Id').present) {
def session = get(Session)
log.info("Listing transactions in session $session.id")
def ledgerService = get(LedgerService)
ledgerService.allTransactions(session).then {
render(Jackson.json(it))
}
} else {
next()
}
}
}
}

View File

@@ -0,0 +1,75 @@
package com.devsoap.dbt.handlers
import com.devsoap.dbt.config.DBTConfig
import com.devsoap.dbt.data.BlockTransaction
import com.devsoap.dbt.data.LedgerData
import com.devsoap.dbt.services.LedgerService
import com.fasterxml.jackson.databind.ObjectMapper
import groovy.util.logging.Log
import groovy.util.logging.Slf4j
import ratpack.config.ConfigData
import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.http.HttpMethod
import ratpack.jackson.Jackson
import ratpack.server.ServerConfig
import ratpack.session.Session
import javax.inject.Inject
@Slf4j
class LedgerUpdateTransactionHandler implements Handler {
private final String executorUrl
@Inject
LedgerUpdateTransactionHandler(DBTConfig config) {
executorUrl = config.executor.remoteUrl
}
@Override
void handle(Context ctx) throws Exception {
ctx.with {
if(ctx.request.method == HttpMethod.POST) {
if(!executorUrl) {
throw new RuntimeException("Executor URL is not set, cannot update transaction")
}
def ledgerService = get(LedgerService)
def session = get(Session)
request.body.then { body ->
def mapper = get(ObjectMapper)
def transaction = mapper.readValue(body.text, BlockTransaction)
log.info("Recieved transaction $transaction.id")
ledgerService.fetchTransaction(session,transaction.id).then {
if(it.present) {
log.info "Transaction $transaction.id exists, updating transaction"
ledgerService.updateTransaction(session, transaction).then {
log.info("Transaction $it updated in ledger")
if(transaction.completed){
log.info("Sending transaction $transaction.id to executor at $executorUrl")
redirect(executorUrl)
} else {
render(Jackson.json(transaction))
}
}
} else {
log.info("Creating new transaction")
ledgerService.newTransaction(session, transaction).then {
log.info("Transaction $it added to ledger")
if(transaction.completed){
log.info("Sending transaction $transaction.id to executor at $executorUrl")
redirect(executorUrl)
} else {
render(Jackson.json(transaction))
}
}
}
}
}
} else {
next()
}
}
}
}

View File

@@ -1,33 +1,35 @@
package com.devsoap.dbt.services package com.devsoap.dbt.services
import com.devsoap.dbt.BlockTransaction import com.devsoap.dbt.data.BlockTransaction
import groovy.util.logging.Log import com.devsoap.dbt.data.LedgerData
import groovy.util.logging.Slf4j
import ratpack.exec.Promise
import ratpack.service.Service import ratpack.service.Service
import ratpack.service.StartEvent import ratpack.session.Session
import ratpack.service.StopEvent
@Log @Slf4j
class LedgerService implements Service { class LedgerService implements Service {
static final transient List<BlockTransaction> transactions = [] private static final LedgerData data = new LedgerData()
BlockTransaction fetchTransaction(String transactionId) { Promise<Optional<BlockTransaction>> fetchTransaction(Session session, String transactionId) {
log.info("Fetching transaction $transactionId") Promise.value(Optional.ofNullable(data.transactions.find {it.id == transactionId}))
log.info("Transactions:$transactions")
transactions.find {it.id == transactionId}
} }
String newTransaction(BlockTransaction transaction) { Promise<List<BlockTransaction>> allTransactions(Session session) {
log.info("Adding new transaction $transaction.id") Promise.value(data.transactions)
transactions << transaction
transaction.id
} }
String updateTransaction(BlockTransaction transaction) { Promise<String> newTransaction(Session session, BlockTransaction transaction) {
log.info("Updating transaction $transaction.id") log.info("Adding new transaction $transaction.id to session ${session.id}")
def existingTransaction = fetchTransaction(transaction.id) data.transactions.add(transaction)
def index = transactions.indexOf(existingTransaction) Promise.value(transaction.id)
transactions.remove(index) }
transactions.add(index, transaction)
Promise<String> updateTransaction(Session session, BlockTransaction transaction) {
log.info("Updating transaction $transaction.id in session ${session.id}")
data.transactions.removeAll {it.id == transaction.id}
data.transactions.add(transaction)
Promise.value(transaction.id)
} }
} }

View File

@@ -1,50 +1,63 @@
package com.devsoap.dbt package com.devsoap.dbt.services
import com.devsoap.dbt.config.DBTConfig import com.devsoap.dbt.config.DBTConfig
import com.devsoap.dbt.data.BlockTransaction
import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import groovy.util.logging.Log import groovy.util.logging.Slf4j
import ratpack.exec.Promise import ratpack.exec.Promise
import ratpack.http.Status
import ratpack.http.client.HttpClient import ratpack.http.client.HttpClient
import ratpack.service.Service import ratpack.service.Service
import javax.inject.Inject import javax.inject.Inject
@Log @Slf4j
class DBTManager implements Service { class TransactionManagerService implements Service {
private final String ledgerUrl
private final HttpClient httpClient private final HttpClient httpClient
private final ObjectMapper mapper private final ObjectMapper mapper
private final DBTConfig config
@Inject @Inject
DBTManager(DBTConfig config, HttpClient httpClient, ObjectMapper mapper){ TransactionManagerService(DBTConfig config, HttpClient httpClient, ObjectMapper mapper){
ledgerUrl = config.ledger.url this.config = config
this.httpClient = httpClient this.httpClient = httpClient
this.mapper = mapper this.mapper = mapper
} }
Promise<JsonNode> execute(ExecuteQuery queryBuilder) { Promise<JsonNode> execute(ExecuteQuery queryBuilder) {
log.info("Executing new transaction") if(!config.ledger.remoteUrl) {
def builder = new TransactionBuilder(this) throw new RuntimeException("Ledger remote url is not set, cannot execute query")
}
def builder = new TransactionBuilder()
queryBuilder.build(builder) queryBuilder.build(builder)
def transaction = builder.build() def transaction = builder.build()
log.info("Sending transaction $transaction.id to ledger") log.info("Sending transaction $transaction.id to ledger at $config.ledger.remoteUrl")
httpClient.post(ledgerUrl.toURI(), { spec -> httpClient.post(config.ledger.remoteUrl.toURI(), { spec ->
spec.body.text(mapper.writeValueAsString(transaction)) spec.body.text(mapper.writeValueAsString(transaction))
}).flatMap { response -> }).onError {
Promise.value(mapper.readTree(response.body.text)) log.error("Failed to send transaction $transaction.id to ledger $config.ledger.remoteUrl")
}.map { response ->
if(response.status == Status.OK) {
mapper.readTree(response.body.text)
}
} }
} }
Promise<JsonNode> execute(String transactionId, ExecuteQuery queryBuilder) { Promise<JsonNode> execute(String transactionId, ExecuteQuery queryBuilder) {
log.info("Amending existing transaction $transactionId") if(!config.ledger.remoteUrl) {
throw new RuntimeException("Ledger remote url is not set, cannot execute query")
}
log.info("Getting transaction $transactionId from ledger") log.info("Sending transaction $transactionId to ledger at $config.ledger.remoteUrl")
httpClient.get(ledgerUrl.toURI(), { spec -> httpClient.get(config.ledger.remoteUrl.toURI(), { spec ->
spec.headers.add('X-Transaction-Id', transactionId) spec.headers.add('X-Transaction-Id', transactionId)
}).flatMap { response -> }).flatMap { response ->
if(response.status != Status.OK) {
throw new RuntimeException("Server returned ${response.statusCode} ${response.status.message}")
}
def oldTransaction = mapper.readValue(response.body.text, BlockTransaction) def oldTransaction = mapper.readValue(response.body.text, BlockTransaction)
if(oldTransaction == null) { if(oldTransaction == null) {
throw new RuntimeException("Transaction with id $transactionId could not be found") throw new RuntimeException("Transaction with id $transactionId could not be found")
@@ -53,7 +66,8 @@ class DBTManager implements Service {
throw new RuntimeException("Cannot modify a completed transaction") throw new RuntimeException("Cannot modify a completed transaction")
} }
def builder = new TransactionBuilder(this, oldTransaction) log.info("Updating transaction $transactionId content with new query")
def builder = new TransactionBuilder(oldTransaction)
queryBuilder.build(builder) queryBuilder.build(builder)
def transaction = builder.build() def transaction = builder.build()
@@ -61,12 +75,14 @@ class DBTManager implements Service {
throw new RuntimeException("Transaction id changed") throw new RuntimeException("Transaction id changed")
} }
log.info("Sending transaction $transactionId to ledger") log.info("Sending updated transaction $transaction.id to ledger at $config.ledger.remoteUrl")
httpClient.post(ledgerUrl.toURI(), { spec -> httpClient.post(config.ledger.remoteUrl.toURI(), { spec ->
spec.body.text(mapper.writeValueAsString(transaction)) spec.body.text(mapper.writeValueAsString(transaction))
}) }).onError {
}.flatMap { response -> log.error("Failed to send transaction $transaction.id to ledger $config.ledger.remoteUrl")
Promise.value(mapper.readTree(response.body.text)) }
}.map { response ->
mapper.readTree(response.body.text)
} }
} }
@@ -74,16 +90,13 @@ class DBTManager implements Service {
private final List<String> queries = [] private final List<String> queries = []
private final DBTManager manager
private final BlockTransaction transaction private final BlockTransaction transaction
private TransactionBuilder(DBTManager manager){ private TransactionBuilder() {
this(manager, new BlockTransaction()) this.transaction = new BlockTransaction()
} }
private TransactionBuilder(DBTManager manager, BlockTransaction transaction) { private TransactionBuilder(BlockTransaction transaction) {
this.manager = manager
this.transaction = transaction this.transaction = transaction
} }

View File

@@ -1,11 +1,13 @@
import com.devsoap.dbt.DBTModule
import com.devsoap.dbt.app.DatabaseService import com.devsoap.dbt.app.DatabaseService
import com.devsoap.dbt.config.DBTConfig import com.devsoap.dbt.config.DBTConfig
import com.devsoap.dbt.DBTManager
import com.devsoap.dbt.handlers.ExecutorHandler import com.devsoap.dbt.handlers.ExecutorHandler
import com.devsoap.dbt.handlers.LedgerHandler
import com.devsoap.dbt.services.LedgerService import com.devsoap.dbt.services.TransactionManagerService
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import org.h2.jdbcx.JdbcDataSource import org.h2.jdbcx.JdbcDataSource
import ratpack.config.ConfigData
import ratpack.session.SessionModule
import javax.sql.DataSource import javax.sql.DataSource
@@ -14,41 +16,29 @@ import static ratpack.groovy.Groovy.ratpack
ratpack { ratpack {
serverConfig { serverConfig {
sysProps()
/** require('/dbt', DBTConfig)
* DBT Framework config
*/
require("", DBTConfig)
} }
bindings { bindings {
bindInstance(new ObjectMapper())
module SessionModule
module (DBTModule) {
it.ledger.remoteUrl = 'http://localhost:5050/ledger'
it.executor.remoteUrl = 'http://localhost:5050/executor'
}
bindInstance(DataSource, new JdbcDataSource(url: 'jdbc:h2:mem:dbtdb;DB_CLOSE_DELAY=-1', user: '')) bindInstance(DataSource, new JdbcDataSource(url: 'jdbc:h2:mem:dbtdb;DB_CLOSE_DELAY=-1', user: ''))
bindInstance(new DatabaseService()) bind DatabaseService
/**
* DBT Framework manager
*/
bind(DBTManager)
bind(LedgerService)
bind(ExecutorHandler)
bind(LedgerHandler)
} }
handlers { handlers {
/**
* DBT Framework handlers
*/
path('executor', ExecutorHandler)
path('ledger', LedgerHandler)
/** /**
* Consumer services * Consumer services
*/ */
get('frontend') { get('frontend') {
get(DBTManager).execute { transaction -> get(TransactionManagerService).execute { transaction ->
transaction.query("INSERT INTO LOGS(LOG_ID,LOG_VALUE) VALUES (${new Random().nextInt()}, 'HELLO')") transaction.query("INSERT INTO LOGS(LOG_ID,LOG_VALUE) VALUES (${new Random().nextInt()}, 'HELLO')")
}.then { }.then {
redirect("/gateway/${it['id'].textValue()}") redirect("/gateway/${it['id'].textValue()}")
@@ -56,7 +46,7 @@ ratpack {
} }
get('gateway/:transactionId?') { get('gateway/:transactionId?') {
get(DBTManager).execute(pathTokens.transactionId, { transaction -> get(TransactionManagerService).execute(pathTokens.transactionId, { transaction ->
transaction.query("INSERT INTO LOGS(LOG_ID,LOG_VALUE) VALUES (${new Random().nextInt()}, 'WORLD')") transaction.query("INSERT INTO LOGS(LOG_ID,LOG_VALUE) VALUES (${new Random().nextInt()}, 'WORLD')")
}).then { }).then {
redirect("/gateway2/${it['id'].textValue()}") redirect("/gateway2/${it['id'].textValue()}")
@@ -64,7 +54,7 @@ ratpack {
} }
get('gateway2/:transactionId?') { get('gateway2/:transactionId?') {
get(DBTManager).execute(pathTokens.transactionId, { transaction -> get(TransactionManagerService).execute(pathTokens.transactionId, { transaction ->
transaction.query("SELECT * FROM LOGS") transaction.query("SELECT * FROM LOGS")
transaction.complete() transaction.complete()
}).then { }).then {

View File

@@ -0,0 +1,31 @@
package com.devsoap.dbt.framework
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import ratpack.impose.Impositions
import ratpack.impose.ServerConfigImposition
import ratpack.server.RatpackServer
class CustomPortMainApplicationUnderTest extends GroovyRatpackMainApplicationUnderTest {
private final int port
CustomPortMainApplicationUnderTest(int port) {
this.port = port
}
@Override
protected Impositions createImpositions() throws Exception {
Impositions.of {
it.add(ServerConfigImposition.of {
it.port(port)
})
}
}
@Override
protected RatpackServer createServer() throws Exception {
System.setProperty('ratpack.dbt.ledger.remoteUrl', "http://localhost:$port/ledger")
System.setProperty('ratpack.dbt.executor.remoteUrl', "http://localhost:$port/executor")
return super.createServer()
}
}

View File

@@ -1,7 +1,7 @@
package com.devsoap.dbt.framework package com.devsoap.dbt.framework
import com.devsoap.dbt.data.BlockTransaction
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import spock.lang.AutoCleanup import spock.lang.AutoCleanup
import spock.lang.Specification import spock.lang.Specification
@@ -9,8 +9,26 @@ class ExecutorSpec extends Specification {
def mapper = new ObjectMapper() def mapper = new ObjectMapper()
def PATH = 'executor'
@AutoCleanup @AutoCleanup
def aut = new GroovyRatpackMainApplicationUnderTest() def aut = new CustomPortMainApplicationUnderTest(8888)
void 'transaction sent to executor'() {
setup:
def transaction = new BlockTransaction()
transaction.execute("SELECT * FROM LOGS")
transaction.end()
when:
String json = aut.httpClient.requestSpec{ spec ->
spec.body.text(mapper.writeValueAsString(transaction))
}.postText(PATH)
def recievedTransaction = mapper.readValue(json, BlockTransaction)
then:
recievedTransaction.id == transaction.id
recievedTransaction.completed
recievedTransaction.executed
}

View File

@@ -1,32 +1,40 @@
package com.devsoap.dbt.framework package com.devsoap.dbt.framework
import com.devsoap.dbt.BlockTransaction import com.devsoap.dbt.data.BlockTransaction
import com.devsoap.dbt.handlers.LedgerHandler
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import ratpack.impose.Imposition
import ratpack.impose.Impositions
import ratpack.impose.ImpositionsSpec
import ratpack.impose.ServerConfigImposition
import ratpack.server.RatpackServer
import spock.lang.AutoCleanup import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification import spock.lang.Specification
class LedgerSpec extends Specification { class LedgerSpec extends Specification {
def mapper = new ObjectMapper() def mapper = new ObjectMapper()
def jsonSlurper = new JsonSlurper()
def PATH = 'ledger'
@AutoCleanup @AutoCleanup
def aut = new GroovyRatpackMainApplicationUnderTest() GroovyRatpackMainApplicationUnderTest aut = new CustomPortMainApplicationUnderTest(8888)
void 'transaction sent to ledger'() { void 'transaction sent to ledger'() {
setup: setup:
def transaction = new BlockTransaction() def transaction = new BlockTransaction()
transaction.execute("SELECT * FROM LOGS") transaction.execute("SELECT * FROM LOGS")
when: when:
def response = mapper.readValue(aut.httpClient.requestSpec { spec -> String json = aut.httpClient.requestSpec{ spec ->
spec.body.text(mapper.writeValueAsString(transaction)) spec.body.text(mapper.writeValueAsString(transaction))
}.post(LedgerHandler.PATH).body.text, BlockTransaction) }.postText(PATH)
def recievedTransaction = mapper.readValue(json, BlockTransaction)
then: then:
response.id == transaction.id recievedTransaction.id == transaction.id
response.completed == false !recievedTransaction.completed
} }
void 'completed transaction marked as completed'() { void 'completed transaction marked as completed'() {
@@ -35,12 +43,13 @@ class LedgerSpec extends Specification {
transaction.execute("SELECT * FROM LOGS") transaction.execute("SELECT * FROM LOGS")
transaction.end() transaction.end()
when: when:
def response = mapper.readValue(aut.httpClient.requestSpec { spec -> String json = aut.httpClient.requestSpec{ spec ->
spec.body.text(mapper.writeValueAsString(transaction)) spec.body.text(mapper.writeValueAsString(transaction))
}.post(LedgerHandler.PATH).body.text, BlockTransaction) }.postText(PATH)
def recievedTransaction = mapper.readValue(json, BlockTransaction)
then: then:
response.id == transaction.id recievedTransaction.id == transaction.id
response.completed == true recievedTransaction.completed
} }
void 'completed transaction sent to executor from ledger'() { void 'completed transaction sent to executor from ledger'() {
@@ -51,7 +60,7 @@ class LedgerSpec extends Specification {
when: when:
def response = mapper.readValue(aut.httpClient.requestSpec { spec -> def response = mapper.readValue(aut.httpClient.requestSpec { spec ->
spec.body.text(mapper.writeValueAsString(transaction)) spec.body.text(mapper.writeValueAsString(transaction))
}.post(LedgerHandler.PATH).body.text, BlockTransaction) }.post(PATH).body.text, BlockTransaction)
then: then:
response.id == transaction.id response.id == transaction.id
response.executed == true response.executed == true
@@ -66,13 +75,16 @@ class LedgerSpec extends Specification {
transaction.execute("SELECT * FROM LOGS") transaction.execute("SELECT * FROM LOGS")
transaction.end() transaction.end()
when: when:
def response = mapper.readValue(aut.httpClient.requestSpec { spec -> String json = aut.httpClient.requestSpec{ spec ->
spec.body.text(mapper.writeValueAsString(transaction)) spec.body.text(mapper.writeValueAsString(transaction))
}.post(LedgerHandler.PATH).body.text, BlockTransaction) }.postText(PATH)
def json = jsonSlurper.parseText(response.queries[1].result) def recievedTransaction = mapper.readValue(json, BlockTransaction)
def result = recievedTransaction.queries[1].result
then: then:
response.id == transaction.id recievedTransaction.id == transaction.id
json.LOG_ID.first() == 1 recievedTransaction.queries.first().result == null // insert query has no result
json.LOG_VALUE.first() == 'HELLO'
result['LOG_ID'].first() == 1
result['LOG_VALUE'].first() == 'HELLO'
} }
} }

View File

@@ -0,0 +1,33 @@
package com.devsoap.dbt.framework
import com.devsoap.dbt.data.BlockTransaction
import com.fasterxml.jackson.databind.ObjectMapper
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import spock.lang.AutoCleanup
import spock.lang.Specification
class TransactionManagementServiceSpec extends Specification {
def mapper = new ObjectMapper()
def PATH = 'ledger'
@AutoCleanup
GroovyRatpackMainApplicationUnderTest aut = new CustomPortMainApplicationUnderTest(8888)
void 'transaction sent to ledger'() {
setup:
def transaction = new BlockTransaction()
transaction.execute("SELECT * FROM LOGS")
when:
String json = aut.httpClient.requestSpec{ spec ->
spec.body.text(mapper.writeValueAsString(transaction))
}.postText(PATH)
def recievedTransaction = mapper.readValue(json, BlockTransaction)
then:
recievedTransaction.id == transaction.id
!recievedTransaction.completed
}
}