Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring classes away from static holders, modernised metrics. #32

Merged
merged 7 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
/logs
/build
/out
/activemq-data
/.gradle

/.idea

## Gradle
/caches
/native
/wrapper
/run
/daemon
build
44 changes: 42 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,58 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [4.0.0]

### Changed

* Refactored the classes and initialisation setup away from static classes.

* Made abandoned transactions tracking configurable.

* Migrated from JMX to Micrometer.

### Migration guide

If you used a custom Spring configuration, it is now the time to move to the autoconfiguration provided by `tw-gaffer-jta-starter`.
Check [docs/usage.md](docs/usage.md) for how to set it up.

> There is some helpful information for migrating to the starter under the changelog for version 2.1.0.

If there is a reason why `tw-gaffer-jta-starter` can not be used, then you can replicate the configuration in `GafferJtaConfiguration` class yourself.

It is also possible to just autoconfigure the transaction management, but not autoconfigure data sources. This can be achieved with property of
`tw-gaffer-jta.config.post-process-beans: false`.

And then, the necessary Gaffer compatible data sources can be created as follows.

```java
@Bean
public GafferJtaDataSource accountsDataSource(GafferTransactionManager transactionManager) {
DataSource innerDataSource = createHikariOrWhatEverDataSource();

GafferJtaDataSource dataSource = new GafferJtaDataSource(transactionManager, "accounts", innerDataSource);
dataSource.setCommitOrder(1);
return dataSource;
}
```

## [3.2.0]

### Changed

* Disabling abandoned transactions tracker.

## [3.1.1]

### Changed

* Add support for spring boot 3.4
* Remove support for spring boot 3.2

## [3.1.0] - 2024-07-18

### Changed

* Run tests against Spring Boot 3.3.
* Baseline moved to Spring Boot 3.2. Library is now built and published against Spring Boot 3.2.

Expand All @@ -30,6 +69,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [3.0.2] - 2024-02-29

### Changed

- Added support for Spring Boot 3.2.
- Updated dependencies.

Expand Down Expand Up @@ -72,7 +112,7 @@ If you were using hardcoded `gafferJtaJtaTransactionManager` bean name in your s

### Added

* Spring Boot auto configuration module `tw-gaffer-jta-starter`.
* Spring Boot autoconfiguration module `tw-gaffer-jta-starter`.

### Changed

Expand All @@ -95,7 +135,7 @@ For that
In Wise context, your data source beans should be just plain `HikariDataSource` instances.
4. Remove the code creating all the beans now defined in the `GafferJtaConfiguration` class.
In a typical Wise service, it comes down to deleting the whole `TransactionManagerConfiguration` class.
5. Add `tw-gaffer-jta` into `runtimeOnly` configuration.
5. Add `tw-gaffer-jta-starter` into `runtimeOnly` configuration.

#### Without Auto Configuration

Expand Down
4 changes: 1 addition & 3 deletions build.common.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ compileTestJava {
options.forkOptions.jvmArgs << '-Xmx256m'
}

tasks.withType(Checkstyle) {
tasks.withType(Checkstyle).configureEach {
config = resources.text.fromFile(file('../google_checks.xml'))

// Flyway requires Vx__ class name, which ofc conflicts with all sensible rules.
Expand Down Expand Up @@ -124,8 +124,6 @@ test {
}

tasks.findAll { it.name.startsWith("spotbugs") }*.configure {
effort = "max"

excludeFilter = file('../spotbugs-exclude.xml')

reports {
Expand Down
15 changes: 11 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import com.github.spotbugs.snom.Confidence
import com.github.spotbugs.snom.Effort
import org.eclipse.jgit.api.errors.RefAlreadyExistsException

plugins {
id "idea"
id 'com.github.spotbugs' version '5.0.14'
id 'org.ajoberstar.grgit' version '5.2.0'
id 'io.github.gradle-nexus.publish-plugin' version "1.1.0"
id 'com.github.spotbugs' version '6.1.4'
id 'org.ajoberstar.grgit' version '5.3.0'
id 'io.github.gradle-nexus.publish-plugin' version "2.0.0"
}

ext.projectName = 'TransferWise Gaffer 1PC JTA'
Expand All @@ -20,7 +22,7 @@ idea.project {
targetBytecodeVersion = JavaVersion.VERSION_17
}

task tagRelease {
tasks.register('tagRelease') {
doLast {
try {
grgit.tag.add {
Expand All @@ -45,3 +47,8 @@ nexusPublishing {
}
}
}

spotbugs {
effort = Effort.valueOf('MAX')
reportLevel = Confidence.valueOf('DEFAULT')
}
3 changes: 3 additions & 0 deletions build.libraries.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ ext {
jakartaTransactionApi : "jakarta.transaction:jakarta.transaction-api",
lombok : 'org.projectlombok:lombok',
mariadbJavaClient : 'org.mariadb.jdbc:mariadb-java-client',
micrometerCore : 'io.micrometer:micrometer-core',
slf4jApi : 'org.slf4j:slf4j-api',
springBootConfigurationProcessor: 'org.springframework.boot:spring-boot-configuration-processor',
springContext : 'org.springframework:spring-context',
springBootStarter : 'org.springframework.boot:spring-boot-starter',
springBootStarterJdbc : 'org.springframework.boot:spring-boot-starter-jdbc',
springBootStarterJpa : 'org.springframework.boot:spring-boot-starter-data-jpa',
springBootStarterActuator : 'org.springframework.boot:spring-boot-starter-actuator',
springBootStarterTest : 'org.springframework.boot:spring-boot-starter-test',
springBootStarterValidation : 'org.springframework.boot:spring-boot-starter-validation',
springOrm : 'org.springframework:spring-orm',
springTx : 'org.springframework:spring-tx',
tomcatJdbc : 'org.apache.tomcat:tomcat-jdbc'
]
Expand Down
2 changes: 1 addition & 1 deletion build.library.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ publishing {
licenses {
license {
name = 'The Apache License, Version 2.0, Copyright 2019 TransferWise Ltd'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
Expand Down
14 changes: 8 additions & 6 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,21 @@ dependencies {

## Configuration in Spring Boot Service

1. Add the `com.transferwise.common:tw-gaffer-jta-starter` dependency.
1. Add the `com.transferwise.common:tw-gaffer-jta-starter` dependency to `runtimeOnly` configuration.
2. Expose all your HikariCP datasource's as beans.
3. `tw-gaffer-jta-starter` Will then automatically wrap all data sources and expose them as beans with Gaffer's `GafferJtaDataSource`.
4. If you need to configure the Gaffer datasource's, you can do so using the `GafferJtaProperties` class*.

\* You can do this in two ways:
1. Create a configuration entry in your `application.yml`, which will then cause the bean to be automatically created. For example:
`tw-gaffer-jta-starter` will automatically wrap all data sources and expose them as beans as Gaffer's `GafferJtaDataSource` instances.

If you need to configure the Gaffer datasource's, you can do so by consulting with the `GafferJtaProperties` class.

For example:

```yaml
tw-gaffer-jta:
core:
log-exceptions: false
databases:
mydb:
commitOrder: 15
connectionValidationInterval: 31s
```
2. Create the `GafferJtaProperties` bean yourself.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=3.2.0
version=4.0.0
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
3 changes: 2 additions & 1 deletion tw-gaffer-jta-starter/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ dependencies {
annotationProcessor libraries.springBootConfigurationProcessor
compileOnly libraries.springBootConfigurationProcessor


implementation libraries.jakartaValidationApi
implementation libraries.hikariCp
implementation libraries.micrometerCore
implementation libraries.springBootStarter
implementation libraries.springBootStarterJdbc
implementation libraries.twBaseUtils

testImplementation libraries.springBootStarterTest

testRuntimeOnly libraries.springBootStarterActuator
testRuntimeOnly libraries.springBootStarterValidation
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.transferwise.common.gaffer.starter;

import com.transferwise.common.gaffer.GafferJtaProperties;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package com.transferwise.common.gaffer.starter;

import com.transferwise.common.gaffer.ServiceRegistry;
import com.transferwise.common.gaffer.ServiceRegistryHolder;
import com.transferwise.common.baseutils.meters.cache.IMeterCache;
import com.transferwise.common.baseutils.meters.cache.MeterCache;
import com.transferwise.common.gaffer.DefaultGafferTransactionManager;
import com.transferwise.common.gaffer.DefaultMetricsTemplate;
import com.transferwise.common.gaffer.GafferJtaProperties;
import com.transferwise.common.gaffer.GafferTransactionManager;
import com.transferwise.common.gaffer.GafferUserTransaction;
import com.transferwise.common.gaffer.MetricsTemplate;
import com.transferwise.common.gaffer.util.Clock;
import com.transferwise.common.gaffer.util.MonotonicClock;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.transaction.TransactionManager;
import jakarta.transaction.UserTransaction;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -16,26 +27,42 @@
public class GafferJtaConfiguration {

@Bean
@ConditionalOnMissingBean
public UserTransaction gafferJtaUserTransaction() {
ServiceRegistry serviceRegistry = ServiceRegistryHolder.getServiceRegistry();
return serviceRegistry.getUserTransaction();
@ConditionalOnMissingBean(IMeterCache.class)
public IMeterCache twDefaultMeterCache(MeterRegistry meterRegistry) {
return new MeterCache(meterRegistry);
}

@Bean
@ConditionalOnMissingBean
public TransactionManager gafferJtaTransactionManager(GafferJtaProperties properties) {
ServiceRegistry serviceRegistry = ServiceRegistryHolder.getServiceRegistry();
serviceRegistry.getConfiguration().setBeforeCommitValidationRequiredTimeMs(properties.getBeforeCommitValidationRequiredTime().toMillis());
return serviceRegistry.getTransactionManager();
@ConditionalOnMissingBean(Clock.class)
public MonotonicClock gafferJtaClock() {
return new MonotonicClock();
}

@Bean
@ConditionalOnMissingBean(MetricsTemplate.class)
public DefaultMetricsTemplate gafferJtaMetricsTemplate(IMeterCache meterCache) {
return new DefaultMetricsTemplate(meterCache);
}

@Bean
@ConditionalOnMissingBean(TransactionManager.class)
public DefaultGafferTransactionManager gafferJtaTransactionManager(GafferJtaProperties gafferJtaProperties, MetricsTemplate metricsTemplate,
Clock clock) {
return new DefaultGafferTransactionManager(gafferJtaProperties, metricsTemplate, clock);
}

@Bean
@ConditionalOnBean(GafferTransactionManager.class)
@ConditionalOnMissingBean(UserTransaction.class)
public UserTransaction gafferJtaUserTransaction(GafferTransactionManager gafferTransactionManager) {
return new GafferUserTransaction(gafferTransactionManager);
}

@Bean("transactionManager")
@ConditionalOnMissingBean
public JtaTransactionManager gafferJtaJtaTransactionManager(UserTransaction userTransaction, TransactionManager transactionManager) {
ServiceRegistry serviceRegistry = ServiceRegistryHolder.getServiceRegistry();
public JtaTransactionManager gafferJtaJtaTransactionManager(UserTransaction userTransaction, GafferTransactionManager transactionManager) {
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, transactionManager);
jtaTransactionManager.setTransactionSynchronizationRegistry(serviceRegistry.getTransactionSynchronizationRegistry());
jtaTransactionManager.setTransactionSynchronizationRegistry(transactionManager.getTransactionSynchronizationRegistry());
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
Expand All @@ -49,6 +76,7 @@ public GafferJtaProperties gafferJtaProperties() {

@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(value = "tw-gaffer-jta.config.post-process-beans", havingValue = "true", matchIfMissing = true)
public static GafferJtaDataSourceBeanProcessor gafferJtaDataSourceBeanProcessor() {
return new GafferJtaDataSourceBeanProcessor();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.transferwise.common.baseutils.ExceptionUtils;
import com.transferwise.common.baseutils.jdbc.DataSourceProxyUtils;
import com.transferwise.common.gaffer.GafferJtaProperties;
import com.transferwise.common.gaffer.GafferTransactionManager;
import com.transferwise.common.gaffer.jdbc.GafferJtaDataSource;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
Expand All @@ -20,16 +22,14 @@ public class GafferJtaDataSourceBeanProcessor implements BeanPostProcessor, Orde
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return ExceptionUtils.doUnchecked(() -> {
if (!(bean instanceof DataSource)) {
if (!(bean instanceof DataSource dataSource)) {
return bean;
}

var dataSource = (DataSource) bean;

if (dataSource.isWrapperFor(GafferJtaDataSource.class)) {
// Why add the starter, if service already wrapped it by itself?
log.warn("Datasource '" + dataSource
+ "' is already wrapped with `DataSourceImpl`. Remove the custom wrapping or `tw-gaffer-jta-starter` dependency.");
log.warn("Datasource '{}'"
+ " is already wrapped with `DataSourceImpl`. Remove the custom wrapping or `tw-gaffer-jta-starter` dependency.", dataSource);
return dataSource;
}

Expand All @@ -44,16 +44,13 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw
}

final var properties = beanFactory.getBean(GafferJtaProperties.class);
final var databaseProperties = properties.getDatabases().get(databaseName);
final var transactionManager = beanFactory.getBean(GafferTransactionManager.class);

var gafferJtaDataSource = new GafferJtaDataSource(dataSource);
DataSourceProxyUtils.tieTogether(gafferJtaDataSource, dataSource);
gafferJtaDataSource.setUniqueName(databaseName);
gafferJtaDataSource.setRegisterAsMBean(false);
final var databaseProperties = properties.getDatabases().get(databaseName);
var gafferJtaDataSource = new GafferJtaDataSource(transactionManager, databaseName, dataSource);

if (databaseProperties != null) {
gafferJtaDataSource.setRegisterAsMBean(databaseProperties.isRegisterAsMbean());
gafferJtaDataSource.setOrder(databaseProperties.getCommitOrder());
gafferJtaDataSource.setCommitOrder(databaseProperties.getCommitOrder());
gafferJtaDataSource.setBeforeReleaseAutoCommitStrategy(databaseProperties.getAutoCommitStrategy());
gafferJtaDataSource.setValidationTimeoutSeconds((int) databaseProperties.getConnectionValidationInterval().toSeconds());
}
Expand Down
Loading
Loading