I'm trying to make an SDK (a custom library) for my Spring Boot microservices using Gradle (with Kotlin DSL) and Kotlin to send emails, reusing most of the stuff and libraries, using AWS SES.
First of all, I don't have a lot of experience on building java libraries, so there could be a lot of stuff that I am not doing correctly. Any feedback is welcome.
Anyways, I want this library to take some properties from the application.yml file on the end application (the microservice app itself) to instantiate some of the Beans of the library.
This library uses JavaEmailService, and has some parameters to use the local email server when developing in local, or the AWS SES implementation for the deployed versions.
For example, in the Bean configuration of the library I have the following code:
@Configuration
internal class BeanConfig {
// ... other properties
@Value("\${sdk.mail.aws.access_key:}")
private lateinit var awsMailAccessKey: String
@Value("\${sdk.mail.aws.secret_key:}")
private lateinit var awsMailSecretKey: String
@Value("\${sdk.mail.aws.region:}")
private lateinit var awsMailRegion: String
@Value("\${sdk.mail.is_local:}")
private var isLocal: Boolean? = null
// ... other bean resolves
@Bean
fun amazonSimpleEmailService(): AmazonSimpleEmailService {
if (isLocal == true) {
return DummyAmazonSimpleEmailServiceImpl()
}
if (awsMailAccessKey.isEmpty() || awsMailSecretKey.isEmpty() || awsMailRegion.isEmpty()) {
throw Exception("Missing AWS parameters (access_key, secret_key, region)")
}
return AmazonSimpleEmailServiceClientBuilder
.standard()
.withCredentials(
AWSStaticCredentialsProvider(
BasicAWSCredentials(
awsMailAccessKey,
awsMailSecretKey
)
)
)
.withRegion(Regions.fromName(awsMailRegion))
.build()
}
}
I want it to take the values from the application.yml file on the end application like so:
sdk:
mail:
is_local: false
aws:
access_key: ${AWS_MAIL_ACCESS_KEY:}
secret_key: ${AWS_MAIL_SECRET_KEY:}
region: ${${AWS_MAIL_REGION:}
I did a custom selection of the dependencies that should be included in the final jar file, as I was getting a lot of ClassNotFoundException (any recommendations on this part are also welcome, in the Java library jar generation). It includes in the final jar all the needed dependencies for it to work.
Here the relevant parts of the build.gradle.kts file for generation of the jar file when I run ./gradlew build:
plugins {
id("org.springframework.boot") version "2.5.0"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.10"
kotlin("plugin.spring") version "1.5.10"
`java-library`
`maven-publish`
}
// ... library version, name and trivial info
dependencies {
implementation(kotlin("stdlib"))
implementation("org.springframework.boot:spring-boot-autoconfigure:2.5.0")
implementation("org.springframework:spring-context-support:5.3.7")
implementation("org.jetbrains.kotlin:kotlin-noarg:1.5.10")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot-starter-mail:2.5.0")
implementation("org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE")
implementation("com.amazonaws:aws-java-sdk-ses:1.12.131")
testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.2")
testImplementation(platform("org.junit:junit-bom:5.7.2"))
testImplementation("io.mockk:mockk:1.10.3-jdk8")
testImplementation("org.junit.platform:junit-platform-launcher:1.7.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.7.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.7.2")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
val exportPackages: List<String> = listOf(
"spring-boot-starter-mail",
"jakarta.mail",
"jakarta.activation",
"spring-context-support",
"spring-boot-starter/",
"spring-cloud-starter-aws",
"spring-cloud-aws-context",
"spring-cloud-aws-core",
"javax.mail-api",
"javax.activation",
"aws-java-sdk-ses",
"aws-java-sdk-core",
"httpclient",
"httpcore",
"joda-time"
)
// Here the custom part
tasks.getByName<Jar>("jar") {
enabled = true
from (
configurations.compileClasspath.get().mapNotNull {
if (exportPackages.any { exportPackage -> it.path.contains(exportPackage) }) {
if (it.isDirectory) it else zipTree(it)
} else null
}
)
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
When running ./gradlew build I get a "sdk-mail-0.0.1-plain.jar" file. This part works pretty well if I included in the final application with that file in the same repo, like so:
implementation(files("lib/sdk-mail-0.0.1-plain.jar"))
THE PROBLEM
When I publish it in our package repository and I import it in the final application like you would do with any other library:
implementation("com.organization:sdk-mail:0.0.1")
It gets imported correctly, and I can use all the classes during development, but when I run the application I found two problems:
application.yml properties defined in the final application (it does find them when running the jar built locally)Here the publish configuration section in the library's build.gradle.kts:
publishing {
repositories {
// ... private repository settings
}
publications {
create<MavenPublication>("default") {
from(components["java"])
}
}
}
What I really don't understand is that, if I download the published jar in the package repository, and add it like if I generated it building the project locally, it works corretly. What I am gessing is that the published version that gradle downloads has some spring boot dependencies or something like that, but I really don't have any clue.
Could someone please help me with that?