Hi
I wanted to share some feedback about real-world usage/dev when working with the java-bindings / java codegen:
The use of Final Classes!
The java bindings in their current state are essentially the equivalent to OpenAPI spec bindings that generate a “Client”: In this case it is a gRPC client. This means the classes are used on the client side and not submitted into a untrusted system (from the point of view of the intended use of the codegen output).
The quality of life issue that comes up for the dev is the current use of Final classes… They are everywhere…
Generated template classes are final
but Choice classes are not final?
and common use classes are final such as the main client:
The issue is about the dev’s ability to extend and enhance:
The generated classes are essentially dtos and are similar to classes that you would generate for a UI or for a HTTP client using OpenAPI codegen. having final on these generated classes does not “secure” anything as it code that would be re-generated on a continual basis as upgrades to the templates occur. We should be able to extend these classes so we can use our own variants. Allowing us to extend or if equivalent interfaces were generated by codegen would allow us to leverage easily leverage IDE codegen and other downstream generation utils as part of the build process.
Second notable example is the DamlLedgerClient. This client wraps all of the interfaces for interacting with the ledger and provides the thread pool, etc.
Given that some interfaces do not exist yet such as the Party Allocation service, it would be nice if we could just extend the DamlLedgerClient instead building one off classes.
Instead of building classes such as:
@ApplicationScoped
class PartyManagementService(
private val damlConfig: DamlConfig
){
private fun getPartyManagementService(accessToken: String? = null): PartyManagementServiceGrpc.PartyManagementServiceFutureStub {
val channel = ManagedChannelBuilder.forAddress(
damlConfig.host().orElseThrow(),
damlConfig.port().orElseThrow()
).usePlaintext().build()
val pms = if (accessToken == null){
PartyManagementServiceGrpc.newFutureStub(channel)
} else {
LedgerCallCredentials.authenticatingStub(PartyManagementServiceGrpc.newFutureStub(channel), accessToken)
}
return pms
}
fun getKnownParties(accessToken: String? = null): Uni<List<PartyManagementServiceOuterClass.PartyDetails>> {
val req = getPartyManagementService(accessToken).listKnownParties(
PartyManagementServiceOuterClass.ListKnownPartiesRequest.getDefaultInstance()
)
return Uni.createFrom().converter(UniRxConverters.fromSingle(), Single.fromFuture(req)).map {
it.partyDetailsList
}
}
fun getParties(parties: List<String>, accessToken: String? = null): Uni<List<PartyManagementServiceOuterClass.PartyDetails>> {
val req = getPartyManagementService(accessToken).getParties(
PartyManagementServiceOuterClass.GetPartiesRequest.newBuilder()
.addAllParties(parties)
.build()
)
return Uni.createFrom().converter(UniRxConverters.fromSingle(), Single.fromFuture(req)).map {
it.partyDetailsList
}
}
/*
* throw PartyNotFoundException if requested party does not exist / could not be found.
* */
fun getParty(party: String, accessToken: String? = null): Uni<PartyManagementServiceOuterClass.PartyDetails> {
return getParties(listOf(party), accessToken).map(Unchecked.function { it ->
if (it.isEmpty()) {
throw PartyNotFoundException(party)
} else {
it.single()
}
})
}
fun getParticipantId(accessToken: String? = null): Uni<String> {
val req = getPartyManagementService(accessToken).getParticipantId(
PartyManagementServiceOuterClass.GetParticipantIdRequest.getDefaultInstance()
)
return Uni.createFrom().converter(UniRxConverters.fromSingle(), Single.fromFuture(req)).map {
it.participantId
}
}
fun allocateParty(displayName: String, partyHint: String? = null, accessToken: String? = null): Uni<PartyManagementServiceOuterClass.PartyDetails> {
val builder = PartyManagementServiceOuterClass.AllocatePartyRequest.newBuilder()
.setDisplayName(displayName)
if (partyHint != null){
builder.partyIdHint = partyHint
}
val req = getPartyManagementService(accessToken).allocateParty(builder.build())
return Uni.createFrom().converter(UniRxConverters.fromSingle(), Single.fromFuture(req)).map {
it.partyDetails
}
}
}
we could have just extended the client and integrate the code, reuse the thread pool, and the other configs.
Recap: The use of final
on classes in the java-bindings feels very heavy handed on the productivity of the development when trying to build from the generated classes and the common classes.
Thanks!