Skip to content

Commit

Permalink
refactor: do not work with reactive stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
janweinschenker committed Feb 21, 2024
1 parent 58a886e commit 50bba70
Show file tree
Hide file tree
Showing 15 changed files with 147 additions and 99 deletions.
2 changes: 1 addition & 1 deletion demo-command/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ import org.springframework.boot.runApplication
open class CommandApplication

fun main(args: Array<String>) {
runApplication<CommandApplication>(*args)
runApplication<CommandApplication>(*args)

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ import io.holixon.cqrshexagonaldemo.demoparent.command.domain.SearchResultItem
import mu.KLogging
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import java.util.function.Consumer

@Service
class NasaApiOutAdapter @Autowired constructor(
val restClient: ReactiveRestClient,
val restClient: RestClient,
val mapper: NasaApiMapper
) : NasaApiOutPort {

companion object : KLogging()

override fun findItemsBySearchTerm(searchTerm: String): Flux<SearchResultItem> {
override fun findItemsBySearchTerm(searchTerm: String): List<SearchResultItem> {
return restClient.getSearchResults(searchTerm)
.stream()
.map { item ->
val links = item.links;
val uriList = links.stream().map(LinkDto::href).toList()
item.data[0].links = links
logSearchResults(uriList, searchTerm)
mapper.toDomainObject(item, CycleAvoidingMappingContext())
};
}
.toList();
}

protected fun logSearchResults(allLinks: Collection<String?>, searchTerm: String?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,78 +1,79 @@
package io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.nasaapi

import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.nasaapi.model.CollectionItemDto
import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.nasaapi.model.SearchResultItemDto
import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.nasaapi.model.SearchResultDto
import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.nasaapi.model.SearchResultItemDto
import io.holixon.cqrshexagonaldemo.demoparent.command.config.ReactiveProperties
import mu.KLogging
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.stereotype.Component
import org.springframework.util.LinkedMultiValueMap
import org.springframework.util.MultiValueMap
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.client.RestTemplate
import org.springframework.web.util.UriComponentsBuilder
import reactor.core.publisher.Flux
import java.net.URI
import java.net.URISyntaxException
import java.util.*
import java.util.function.Consumer

/**
* [API docs](https://images.nasa.gov/docs/images.nasa.gov_api_docs.pdf)
*/
@Component
class ReactiveRestClient @Autowired constructor(webBuilder: WebClient.Builder, var properties: ReactiveProperties) {

private val webClient: WebClient = webBuilder.build()
class RestClient @Autowired constructor(
restTemplateBuilder: RestTemplateBuilder,
var properties: ReactiveProperties
) {
private val restTemplate: RestTemplate

companion object : KLogging()

fun getSearchResults(searchTerm: String?): Flux<SearchResultItemDto> {
init {
restTemplate = restTemplateBuilder.build()
}

fun getSearchResults(searchTerm: String?): List<SearchResultItemDto> {
return getSearchResults(searchTerm, null, null)
.map { item: SearchResultItemDto ->
check(!item.data[0].nasaId.contains("GS")) { "error" }
item
}
}

fun getSearchResults(searchTerm: String?, page: Long?, pageSize: Long?): Flux<SearchResultItemDto> {
val queryParams: LinkedMultiValueMap<String?, String?> = LinkedMultiValueMap<String?, String?>()
fun getSearchResults(searchTerm: String?, page: Long?, pageSize: Long?): List<SearchResultItemDto> {
val queryParams = LinkedMultiValueMap<String, String?>()
queryParams.add("q", searchTerm)
val nullSafePage = Optional.ofNullable(page)
.orElse(1L)
.toString()
queryParams.add("page", nullSafePage)
//queryParams.add("api_key", properties.getApiKey());
val nullSafePageSize = Optional.ofNullable(pageSize)
.orElse(12L)
.toString()
queryParams.add("page_size", nullSafePageSize)
//queryParams.add("api_key", properties.getApiKey());
return webClient
.get()
.uri(buildUri(queryParams))
.headers(Consumer<HttpHeaders> { h: HttpHeaders -> h.addAll(prepareHttpHeaders()) })
.retrieve()
.bodyToFlux(SearchResultDto::class.java)
.doOnNext { searchResult ->
val collection: CollectionItemDto = searchResult.collection
val size = collection.items.size
logger.debug(
"response size: {} / page number {} / page size {} / has next page: {}",
size,
nullSafePage,
nullSafePageSize,
hasNextPage(collection)
)
}
.flatMapIterable { searchResult -> searchResult.collection.items }
.onBackpressureBuffer()
val apiResponse = restTemplate.exchange(
buildUri(queryParams),
HttpMethod.GET,
HttpEntity<SearchResultDto>(prepareHttpHeaders()),
SearchResultDto::class.java
)
val collection = apiResponse.body?.collection
val size = collection?.items?.size
logger.info(
"response size: {} / page number {} / page size {} / has next page: {}",
size,
nullSafePage,
nullSafePageSize,
hasNextPage(collection)
)
return apiResponse.body?.collection?.items ?: ArrayList()
}

private fun hasNextPage(collection: CollectionItemDto): Boolean {
return collection.links
.stream()
.anyMatch { l -> "next" == l.rel }
fun hasNextPage(collection: CollectionItemDto?): Boolean {
return collection?.links?.stream()?.anyMatch { l -> "next" == l.rel } ?: false
}

fun buildUri(queryParams: MultiValueMap<String?, String?>?): URI {
fun buildUri(queryParams: MultiValueMap<String, String?>?): URI {
return try {
val uri = URI(
properties.nasaApiSchema, properties.nasaApiHostname,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
package io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult

import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult.entity.SearchResultItemEntity
import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult.mapper.EntityMapper
import io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult.repo.ItemRepository
import io.holixon.cqrshexagonaldemo.demoparent.command.application.port.out.searchresult.SearchResultOutPort
import io.holixon.cqrshexagonaldemo.demoparent.command.domain.SearchResultItem
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers

@Service
class SearchResultOutAdapter @Autowired constructor(
val mapper: EntityMapper,
val itemRepository: ItemRepository
) : SearchResultOutPort {

override fun saveSearchResult(searchResultItem: SearchResultItem): Mono<SearchResultItem> {
override fun saveSearchResult(searchResultItem: SearchResultItem): SearchResultItem {
val itemEntity = mapper.toEntity(searchResultItem)
itemEntity.data.forEach { dataItemEntity ->
itemEntity.data?.forEach { dataItemEntity ->
dataItemEntity.searchResultItem = itemEntity
dataItemEntity.links.forEach { linkEntity ->
dataItemEntity.links?.forEach { linkEntity ->
linkEntity.dataItem = dataItemEntity
}
}

return Mono
.fromCallable { itemRepository.save(itemEntity) }
.subscribeOn(Schedulers.boundedElastic())
.map { savedEntity -> mapper.toDomainObject(savedEntity) }
val savedEntity: SearchResultItemEntity = itemRepository.save(itemEntity)
val toDomainObject = mapper.toDomainObject(savedEntity)
return toDomainObject
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
package io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult.entity

import jakarta.persistence.Column
import jakarta.persistence.EntityListeners
import jakarta.persistence.MappedSuperclass
import org.springframework.data.annotation.CreatedBy
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedBy
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.io.Serializable
import java.time.Instant

@MappedSuperclass
abstract class BaseEntity : Serializable {

@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity(
@CreatedDate
@Column(updatable = false)
var created: Instant? = null
open var created: Instant?,

@LastModifiedDate
val updated: Instant? = null
open val updated: Instant?,

@CreatedBy
@Column(updatable = false)
var createdBy: String? = null
open var createdBy: String?,

@LastModifiedBy
val updatedBy: String? = null
open val updatedBy: String?

) : Serializable {


}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,20 @@ class DataItemEntity(
var nasaId: String,
@Column
var title: String,
@JoinColumn(nullable = false, name = "search_result_item_id", foreignKey = ForeignKey(name = "FK_DATA_ITEM__SEARCH_RESULT_ITEM"))
@JoinColumn(
nullable = false,
name = "search_result_item_id",
foreignKey = ForeignKey(name = "FK_DATA_ITEM__SEARCH_RESULT_ITEM")
)
@ManyToOne(fetch = FetchType.LAZY)
var searchResultItem: SearchResultItemEntity,
@OneToMany(fetch = FetchType.EAGER, mappedBy = "dataItem")
var links: Set<LinkEntity>
) {
var searchResultItem: SearchResultItemEntity?,
@OneToMany(fetch = FetchType.EAGER, mappedBy = "dataItem", cascade = [CascadeType.PERSIST])
var links: Set<LinkEntity>?,
created: Instant?,
updated: Instant?,
createdBy: String?,
updatedBy: String?

) : BaseEntity(created, updated, createdBy, updatedBy) {

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult.entity

import jakarta.persistence.*
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.Instant

@EntityListeners(AuditingEntityListener::class)
@Entity
@Table(
name = "link",
Expand All @@ -20,6 +23,11 @@ class LinkEntity(
var render: String?,
@JoinColumn(nullable = false, name = "data_item_id", foreignKey = ForeignKey(name = "FK_LINK__DATA_ITEM"))
@ManyToOne(fetch = FetchType.LAZY)
var dataItem: DataItemEntity
) : BaseEntity() {
var dataItem: DataItemEntity?,
created: Instant?,
updated: Instant?,
createdBy: String?,
updatedBy: String?

) : BaseEntity(created, updated, createdBy, updatedBy) {
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
package io.holixon.cqrshexagonaldemo.demoparent.command.adapter.out.searchresult.entity

import jakarta.persistence.*
import java.time.Instant


@Entity
@Table(
name = "search_result_item",
schema = "command"
)
class SearchResultItemEntity(

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
var id: Long,
@OneToMany(fetch = FetchType.EAGER, mappedBy = "searchResultItem")
var data: Set<DataItemEntity>,

@OneToMany(fetch = FetchType.EAGER, mappedBy = "searchResultItem", cascade = [CascadeType.PERSIST])
var data: Set<DataItemEntity>?,

@Column(nullable = false)
var href: String,
created: Instant?,
updated: Instant?,
createdBy: String?,
updatedBy: String?

) : BaseEntity() {
) : BaseEntity(created, updated, createdBy, updatedBy) {
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
package io.holixon.cqrshexagonaldemo.demoparent.command.application

import io.holixon.cqrshexagonaldemo.demoparent.command.application.port.out.nasaapi.NasaApiOutPort
import io.holixon.cqrshexagonaldemo.demoparent.command.application.port.out.searchresult.SearchResultOutPort
import mu.KLogging
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import java.util.stream.Stream

@Service
open class CommandService @Autowired constructor(val nasaApi: NasaApiOutPort) {
open class CommandService @Autowired constructor(
val nasaApi: NasaApiOutPort,
val outPort: SearchResultOutPort
) {

companion object : KLogging()

@Async
@EventListener
fun init(applicationReadyEvent: ApplicationReadyEvent) {
findItems("Ceres")
}

fun findItems(searchTerm: String) {
nasaApi.findItemsBySearchTerm(searchTerm)
.doOnNext { item ->
val dataItem = item.data[0]
val toList = nasaApi.findItemsBySearchTerm(searchTerm)
.stream()
.map { searchResultItem -> outPort.saveSearchResult(searchResultItem) }
.map { searchResultItem ->
val dataItem = searchResultItem.data[0]
logger.info("nasaId: {} - title: {}", dataItem.nasaId, dataItem.title)
dataItem
}
.flatMapIterable { item -> item.links }
.subscribe { link -> logger.info("uri {}", link.href) }
.flatMap { item -> item.links?.stream() ?: Stream.empty() }
.toList()
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import io.holixon.cqrshexagonaldemo.demoparent.command.domain.SearchResultItem
import reactor.core.publisher.Flux

fun interface NasaApiOutPort {
fun findItemsBySearchTerm(searchTerm: String): Flux<SearchResultItem>
fun findItemsBySearchTerm(searchTerm: String): List<SearchResultItem>
}
Loading

0 comments on commit 50bba70

Please sign in to comment.