Skip to content
This repository has been archived by the owner on Feb 12, 2024. It is now read-only.

Dropwizard unit of work #50

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

isopropylcyanide
Copy link

@isopropylcyanide isopropylcyanide commented Jan 15, 2022

@JdbiUnitOfWork - Unit of Work Support

Further to our discussion on #14 with @joschi , adding an initial implementation. from the original source https://github.com/isopropylcyanide/dropwizard-jdbi-unitofwork. I've blogged about it as well. Tweet

This Provides a Unit of Work annotation for a Jdbi backed Dropwizard service for wrapping resource methods in a transaction context

  • Dropwizard provides a very slick @UnitOfWork annotation that wraps a transaction context around resource methods annotated with this annotation. This is very useful for wrapping multiple calls in a single database transaction all of which will succeed or roll back atomically.

  • However this support is only available for Hibernate. This module provides support for a Jdbi backend

image

Features

  • transactional across multiple datasources when called from a request thread
  • transactional across multiple datasources across multiple threads
  • excluding selectively, certain set of URI's from transaction contexts, such as ELB, Health Checks etc
  • Http GET methods are excluded from transaction by default.
  • Http POST methods are wrapped around in a transaction only when annotated with @JdbiUnitOfWork

Usage

  • There's a separate README that I've added.
  • I have two examples (one with DW and one with Jersey/Guice), here. Not sure if we should add any examples here to dropwizard-jdbi

Motivation

  • With the project being in stable use at our production for well over 18 months, I'd like to contribute upstream to Jdbi-2 and eventually to JDBI-3 if this PR gets merged. I feel this would be a valuable feature add for folks looking to get a UnitOfWork working for JDBI

All that's required is to

  • add the dropwizard-jdbi dependency (no op for existing consumers)
  • Create an instance of JdbiUnitOfWorkProvider
  • Specify where the DAO / SQL Objects
  • Register the listener with Jersey
  • Done. Can use @JdbiUnitOfWork

Disclaimer

I'm not well versed with the whole versioning thing going on with Dropwizard (1.3 -> 2.0). I'd need your help in getting this reviewed and possibly merged. Thank you.

@@ -78,6 +78,7 @@
<sonar.host.url>https://sonarcloud.io</sonar.host.url>

<jdbi.version>2.78</jdbi.version>
<org.reflections.version>0.10.2</org.reflections.version>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need reflections for helping users take in a list of packages and automagically return all "Dao" specific classes annotated with SqlUpdate / SqlQuery / SqlCall / SqlBatch` etc.

import java.util.stream.Collectors;

@SuppressWarnings({"UnstableApiUsage", "rawtypes", "unchecked"})
public class JdbiUnitOfWorkProvider {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only point of access for the users unless they want to do something low level for which a getter is exposed getHandleManager()

* dao requests between multiple threads.
* Note: Not suitable when you can not set the name format for the newly spawned threads.
**/
class LinkedRequestScopedJdbiHandleManager implements JdbiHandleManager {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users would use this as follows

JdbiUnitOfWorkProvider provider = JdbiUnitOfWorkProvider.withLinked(dbi); 
...

RequestEvent.Type type = event.getType();
log.debug("Handling GET Request Event {} {}", type, Thread.currentThread().getId());

if (type == RequestEvent.Type.FINISHED) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialisation would be done by dao access methods. Every dao access goes through a handle manager to get the handle.

We make sure to close the handle at the end of the request lifecycle.

throw new IllegalArgumentException(String.format("Class [%s] has no method annotated with a Jdbi SQL Object", daoClass.getSimpleName()));
}

log.info("Binding class [{}] with proxy handler [{}] ", daoClass.getSimpleName(), handleManager.getClass().getSimpleName());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the app starts and the users have configured a provider, it looks like

INFO  [2022-01-15 19:21:16,787] io.dropwizard.server.DefaultServerFactory: Registering jersey handler with root path prefix: /
INFO  [2022-01-15 19:21:16,790] io.dropwizard.server.DefaultServerFactory: Registering admin handler with root path prefix: /
...
INFO  [2022-01-15 19:21:16,848] com.github.isopropylcyanide.jdbiunitofwork.core.JdbiUnitOfWorkProvider: Binding class [AppDao] with proxy handler [LinkedRequestScopedJdbiHandleManager] 
INFO  [2022-01-15 19:21:16,855] com.github.isopropylcyanide.jdbiunitofwork.core.JdbiUnitOfWorkProvider: Binding class [CountingDao] with proxy handler [LinkedRequestScopedJdbiHandleManager] 
...
INFO  [2022-01-15 19:21:16,859] io.dropwizard.server.ServerFactory: Starting SampleApplication

if (excludedPaths.stream().anyMatch(path::contains)) {
return null;
}
if (event.getContainerRequest().getMethod().equals(HttpMethod.GET)) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not every request would like to get itself attached to GET/POST listeners, which is why consumers have a way to exclude certain paths from this listener.


private DBI dbi;

private LinkedRequestScopedJdbiHandleManager manager;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is all about testing whether multiple threads can share a common thread handle safely

@isopropylcyanide
Copy link
Author

@joschi Mind taking a stab at this? Would appreciate it. Thanks

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant