Skip to content

Make the 51Did Java client asynchronous only - #129

Open
jwrosewell wants to merge 1 commit into
mainfrom
feature/did-client-async
Open

Make the 51Did Java client asynchronous only#129
jwrosewell wants to merge 1 commit into
mainfrom
feature/did-client-async

Conversation

@jwrosewell

Copy link
Copy Markdown
Contributor

What changed

Every method on DidClient that reaches the cloud now returns a CompletableFuture and the synchronous forms are removed outright. This is one of a set of changes making every 51Did client and every OWID port asynchronous where the network is involved, so the libraries behave the same way in every language.

Methods that never touch the network are untouched, so getResourceKey, getEndpoint, hasLicenceKey and the statics parseKeys, inForceAt, candidatesFor, resolveEndpoint and encode are as they were. FodId is not touched, because another branch is changing it.

Removed

public List<SigningKey> publicKeys() throws IOException
public SigningKey publicKeyFor(FodId fodId) throws IOException
public boolean verifySignature(FodId fodId) throws IOException
public SignatureCheck verifySignatureDetailed(FodId fodId) throws IOException
public boolean verify(FodId fodId) throws IOException
public boolean verify(String fodId) throws IOException
public RedeemResult redeem(FodId fodId, String result, String challenge) throws IOException
public RedeemResult redeem(String fodId, String result, String challenge) throws IOException
Response send(Request request) throws IOException                      // HttpTransport

New

The same eight, each returning CompletableFuture of what it returned before, plus:

CompletableFuture<Response> send(Request request)   // HttpTransport
public Builder executor(Executor executor)          // new builder step

The build stays at Java 8, which has no non-blocking HTTP client in the standard library, so the default transport runs the existing HttpURLConnection exchange on an Executor backed by a bounded pool of daemon threads. The documentation says plainly that this is blocking work on a background thread, and that a caller on Java 11 or later can supply a transport over java.net.http.HttpClient.sendAsync instead. The executor belongs to the default transport; a transport of your own schedules its own work.

Failure contract

Every failure the client reports is now the cause of a CompletionException, including input it refuses before the cloud is called, so verify(String) and redeem(...) no longer throw IllegalArgumentException at the caller. A transport that throws instead of failing its future, or that answers null, is reported the same way rather than leaving a caller waiting for ever.

Kept exactly

The two step verification, licence key handling, the redeem outcome types, key selection by the latest start at or before the date, and the 401 handling, all still pinned by their original tests. The key list cache now shares one in-flight fetch between concurrent callers.

Verification

Command Result
mvn -pl pipeline.did -am test 132 run, 0 failures, 0 errors, 2 skipped (live tests, no resource key)
mvn -DskipTests package success across all 13 modules, so nothing else called a removed method
mvn -pl ...developer-examples.fodid -am test 9 run, 0 failures

DidClientTests went from 63 tests to 68. The new ones cover two concurrent key list requests making one HTTP call, a failed shared fetch reaching every waiter and leaving the next caller to start its own, the builder's executor receiving the blocking work, and the default transport reporting both a failed exchange and a refused executor through the future. Thirteen tests whose names said "Raises" now say "Fails", since they no longer test a thrown exception.

The module compiles under the project's -Xlint:all with -Werror on JDK 21. The URL is built with URI.create(...).toURL() rather than the URL(String) constructor, which is deprecated from Java 20 and would fail that leg.

Notes

Written with AI assistance and reviewed before merging. This client uses only the OWID library's cryptography and not its fetch, so it does not wait on the OWID asynchronous work in SWAN-community.

Every DidClient method that can reach the 51Degrees cloud now answers
through a CompletableFuture and the blocking versions are gone, so a
server handling a request never holds its thread while the cloud is
asked. publicKeys, publicKeyFor, verifySignature,
verifySignatureDetailed, verify and redeem keep their names and their
arguments and return a CompletableFuture of what they returned before.
None of them declares IOException any more, and nothing they refuse
before the cloud is called is thrown either, so every failure reaches a
caller the same way, as the cause of the CompletionException a future
reports.

HttpTransport.send answers a CompletableFuture<Response> as well. Java 8
has no non-blocking HTTP client in its standard library, so the default
transport still uses HttpURLConnection and runs the exchange on an
executor, which the builder can now be given with executor(Executor) and
which is otherwise a small bounded pool of daemon threads. On Java 11 and
later a transport over java.net.http.HttpClient.sendAsync needs no thread
per request.

The key cache works as it did, except that callers arriving while a fetch
is under way now wait on that one fetch rather than starting another,
which matters more now that several requests can be in flight at once.
The two step verification, the licence key handling, the redeem outcome
types, the key selection by latest start at or before the date and the
handling of every status the cloud answers with are all unchanged, and
the tests that pin them are converted rather than rewritten.

The demo server's /redeem route composes the two calls and maps a failure
in one place, and the READMEs and the package documentation describe the
asynchronous client and how to move off the blocking one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant