Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin and Spring boot a pleasant experience an...

Karumi
March 29, 2019

Kotlin and Spring boot a pleasant experience and some rough edges

In the last months, we have been working on Karumi with Spring boot building web services with Kotlin. The experience has been quite pleasant. I will explain how we have used Kotlin to write quality backend code, how to write tests and how to get the most out of both technologies.

Besides, we will explain some problems we have had in the development of the applications and how we have solved them.

Karumi

March 29, 2019
Tweet

More Decks by Karumi

Other Decks in Technology

Transcript

  1. Kotlin and Spring boot a pleasant experience and some rough

    edges Jorge Juan Barroso Carmona Developer and GDE [email protected] @flipper83 https://greach.contestia.es
  2. Testing for android & iOS. Trainings Architecture, Patterns and principles

    for Android & iOS. Mastering Git. Advanced mobile development. Testing for android & iOS. Architecture, Patterns and principles for Android & iOS. Companies For Everybody
  3. Adam Tornhill “Computer languages differ not so much in what

    they make possible, but in what they make easy.” Larry Wall
  4. @RestController class ProjectController( private val obtainProjects: ObtainProjects ) { @GetMapping("/projects")

    fun getProjectsEndpoint( authentication: Authentication ) = obtainProjects( authentication.getCurrentUser() ).fold( ifRight{ ResponseEntity.ok(it.mapToApi()) }, ifLeft = { it.toErrorApi() } ) }
  5. @RestController class ProjectController( private val obtainProjects: ObtainProjects ) { @GetMapping("/projects")

    fun getProjectsEndpoint( authentication: Authentication ) = obtainProjects( authentication.getCurrentUser() ).fold( ifRight{ ResponseEntity.ok(it.mapToApi()) }, ifLeft = { it.toErrorApi() } ) }
  6. @RestController class ProjectController( private val obtainProjects: ObtainProjects ) { @GetMapping("/projects")

    fun getProjectsEndpoint( authentication: Authentication ) = obtainProjects( authentication.getCurrentUser() ).fold( ifRight{ ResponseEntity.ok(it.mapToApi()) }, ifLeft = { it.toErrorApi() } ) }
  7. @Component class ObtainProjects( val projectRepository: ProjectRepository ) { operator fun

    invoke( currentUser: User ): Either<ProjectError, Projects> = projectRepository.getAll(currentUser) }
  8. @Component //@Service class ObtainProjects( val projectRepository: ProjectRepository ) { operator

    fun invoke( currentUser: User ): Either<ProjectError, Projects> = projectRepository.getAll(currentUser) }
  9. @Component class ObtainProjects( val projectRepository: ProjectRepository ) { operator fun

    invoke( currentUser: User ): Either<ProjectError, Projects> = projectRepository.getAll(currentUser) }
  10. @Repository class ProjectRepository( private val projectDataSource: ProjectDataSource ) { fun

    getAll( currentUser: User ): Either<ProjectError, Projects> = TryLogger { Projects( projects = projectDataSource .findByOwner( userEntity = currentUser.mapToEntity() ) .mapToDomain() ) }.toEither { ProjectError.DbStorageError } }
  11. @Repository class ProjectRepository( private val projectDataSource: ProjectDataSource ) { fun

    getAll( currentUser: User ): Either<ProjectError, Projects> = TryLogger { Projects( projects = projectDataSource .findByOwner( userEntity = currentUser.mapToEntity() ) .mapToDomain() ) }.toEither { ProjectError.DbStorageError } }
  12. /**/ Projects( projects = projectDataSource .findByOwner( userEntity = currentUser.mapToEntity() )

    .mapToDomain() ) /**/ fun User.mapToEntity(): UserEntity = UserEntity( _id = this.id, name = this.name, email = this.email, username = this.username )
  13. @Repository class ProjectRepository( private val projectDataSource: ProjectDataSource ) { fun

    getAll( currentUser: User ): Either<ProjectError, Projects> = TryLogger { Projects( projects = projectDataSource .findByOwner( userEntity = currentUser.mapToEntity() ) .mapToDomain() ) }.toEither { ProjectError.DbStorageError } }
  14. @Repository class ProjectRepository( private val projectDataSource: ProjectDataSource ) { fun

    getAll( currentUser: User ): Either<ProjectError, Projects> = TryLogger { Projects( projects = projectDataSource .findByOwner( userEntity = currentUser.mapToEntity() ) .mapToDomain() ) }.toEither { ProjectError.DbStorageError } }
  15. @Repository class ProjectRepository( private val projectDataSource: ProjectDataSource ) { fun

    getAll( currentUser: User ): Either<ProjectError, Projects> = TryLogger { Projects( projects = projectDataSource .findByOwner( userEntity = currentUser.mapToEntity() ) .mapToDomain() ) }.toEither { ProjectError.DbStorageError } }
  16. sealed class DomainError( val message: String ) sealed class ProjectError(

    message: String ) : DomainError(message, field) { object DbStorageError : ProjectError( message = "Error accessing to the db” ) class NotFound( field: String ) : ProjectError(message = "$field not found”) }
  17. @Component class ObtainProjects( val projectRepository: ProjectRepository ) { operator fun

    invoke( currentUser: User ): Either<ProjectError, Projects> = projectRepository.getAll(currentUser) }
  18. @RestController class ProjectController( private val obtainProjects: ObtainProjects ) { @GetMapping("/projects")

    fun getProjectsEndpoint( authentication: Authentication ) = obtainProjects( authentication.getCurrentUser() ).fold( ifRight = { ResponseEntity.ok(it.mapToApi()) }, ifLeft = { it.toErrorApi() } ) }
  19. fun ProjectError.toErrorApi(): ResponseEntity<String> = when (this) { ProjectError.DbStorageError -> ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)

    is ProjectError.NotFound -> ResponseEntity.status(HttpStatus.NOT_FOUND }.body(createErrorMessage(this)) fun createErrorMessage( projectError: ProjectError ): String = projectError.toJson()
  20. fun ProjectError.toErrorApi(): ResponseEntity<String> = when (this) { ProjectError.DbStorageError -> ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)

    is ProjectError.NotFound -> ResponseEntity.status(HttpStatus.NOT_FOUND }.body(createErrorMessage(this)) fun createErrorMessage( projectError: ProjectError ): String = projectError.toJson()
  21. @RestController class ProjectController( private val obtainProjects: ObtainProjects ) { @GetMapping(“/projects”)

    // @Secured({"ROLE_ADMIN", "ROLE_USER"}) // @PreAuthorize(“hasRole(‘ADMIN')") fun getProjectsEndpoint( authentication: Authentication ) = obtainProjects( authentication.getCurrentUser() ).fold( ifRight{ ResponseEntity.ok(it.mapToApi()) }, ifLeft = { it.toErrorApi() } ) }
  22. class JwtTokenFilter( private val jwtTokenProvider: JwtTokenProvider) : OncePerRequestFilter() { @Throws(ServletException::class,

    IOException::class) override fun doFilterInternal( httpServletRequest: HttpServletRequest, httpServletResponse: HttpServletResponse, filterChain: FilterChain ) { val token = jwtTokenProvider.resolveToken(httpServletRequest) if (token == null) { filterChain.doFilter(httpServletRequest, httpServletResponse) return } if (jwtTokenProvider.validateToken(token)) { val auth = jwtTokenProvider.getAuthentication(token) SecurityContextHolder.getContext().authentication = auth } filterChain.doFilter(httpServletRequest, httpServletResponse) } }
  23. @Component class UploadVideo( val projectRepository: ProjectRepository, val storageService: StorageService, val

    videoRepository: VideoRepository ) { operator fun invoke(newVideo: NewVideo): Either<ProjectError, Video> = binding { if (!validateVideo(newVideo)) ProjectError.VideoFormatError.left().bind() val project = projectRepository[newPhoto.projectId] .bind() .toEither { ProjectError.NotFound("project") } .bind() val videoStorageInfo = storageService .storePhoto( generateName(project.id), video ).bind() photoRepository.add(VideoToStore( filename = videoStorageInfo.filename, videoUrl = videoStorageInfo.url, owner = newVideo.owner, projectId = projectId )).bind() }
  24. @Component class UploadVideo( val projectRepository: ProjectRepository, val storageService: StorageService, val

    videoRepository: VideoRepository ) { operator fun invoke(newVideo: NewVideo): Either<ProjectError, Video> = binding { if (!validateVideo(newVideo)) ProjectError.VideoFormatError.left().bind() val project = projectRepository[newPhoto.projectId] .bind() .toEither { ProjectError.NotFound("project") } .bind() val videoStorageInfo = storageService .storePhoto( generateName(project.id), video ).bind() photoRepository.add(VideoToStore( filename = videoStorageInfo.filename, videoUrl = videoStorageInfo.url, owner = newVideo.owner, projectId = projectId )).bind() }
  25. @Component class UploadVideo( val projectRepository: ProjectRepository, val storageService: StorageService, val

    videoRepository: VideoRepository ) { operator fun invoke(newVideo: NewVideo): Either<ProjectError, Video> = binding { if (!validateVideo(newVideo)) ProjectError.VideoFormatError.left().bind() val project = projectRepository[newPhoto.projectId] .bind() .toEither { ProjectError.NotFound("project") } .bind() val videoStorageInfo = storageService .storePhoto( generateName(project.id), video ).bind() photoRepository.add(VideoToStore( filename = videoStorageInfo.filename, videoUrl = videoStorageInfo.url, owner = newVideo.owner, projectId = projectId )).bind() }
  26. @Component class UploadVideo( val projectRepository: ProjectRepository, val storageService: StorageService, val

    videoRepository: VideoRepository ) { operator fun invoke(newVideo: NewVideo): Either<ProjectError, Video> = binding { if (!validateVideo(newVideo)) ProjectError.VideoFormatError.left().bind() val project = projectRepository[newPhoto.projectId] .bind() .toEither { ProjectError.NotFound("project") } .bind() val videoStorageInfo = storageService .storePhoto( generateName(project.id), video ).bind() photoRepository.add(VideoToStore( filename = videoStorageInfo.filename, videoUrl = videoStorageInfo.url, owner = newVideo.owner, projectId = projectId )).bind() }
  27. @Component class UploadVideo( val projectRepository: ProjectRepository, val storageService: StorageService, val

    videoRepository: VideoRepository ) { operator fun invoke(newVideo: NewVideo): Either<ProjectError, Video> = binding { if (!validateVideo(newVideo)) ProjectError.VideoFormatError.left().bind() val project = projectRepository[newPhoto.projectId] .bind() .toEither { ProjectError.NotFound("project") } .bind() val videoStorageInfo = storageService .storePhoto( generateName(project.id), video ).bind() photoRepository.add(VideoToStore( filename = videoStorageInfo.filename, videoUrl = videoStorageInfo.url, owner = newVideo.owner, projectId = projectId )).bind() }
  28. @Component class UploadVideo( val projectRepository: ProjectRepository, val storageService: StorageService, val

    videoRepository: VideoRepository ) { operator fun invoke(newVideo: NewVideo): Either<ProjectError, Video> = binding { if (!validateVideo(newVideo)) ProjectError.VideoFormatError.left().bind() val project = projectRepository[newPhoto.projectId] .bind() .toEither { ProjectError.NotFound("project") } .bind() val videoStorageInfo = storageService .storePhoto( generateName(project.id), video ).bind() photoRepository.add(VideoToStore( filename = videoStorageInfo.filename, videoUrl = videoStorageInfo.url, owner = newVideo.owner, projectId = projectId )).bind() }