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

When Clean Architecture met Kotlin: A love story

When Clean Architecture met Kotlin: A love story

When Clean Architecture met Kotlin: A love story
Kotlin, a modern language bursts into the community when developers have been perfecting what they call Clean Architecture for years, some potions and spells with which to increase the power of testability and become independent of evil external agents and cruel frameworks.
While large groups begin to praise the bonanzas of lambdas, extension functions, null treatment and other songs of sirens, this language will have to fight to show that it is not just a nice syntax and promises of summer, but has come to take Clean Architecture to a new level.
Corutines, polymorphic types, high order functions, pattern matching and many other ideas and solutions are what Kotlin offers to us, and we will review them to see how to use them and continue having a Clean Architecture.

Nicolás Patarino

March 02, 2018
Tweet

More Decks by Nicolás Patarino

Other Decks in Programming

Transcript

  1. When Clean Architecture Met Kotlin A love story A better

    lovestory than Twilight MONGUERS MOBILE PRESENTS A WRAPPER BROS. PRODUCTION IN ASSOCIATION WITH METRO GOLDWYN ARROW. COSTUME DESIGNER JAVISON FORD, EDITED BY KATHECLEAN HEPBURN, MUSIC BY RYAN KOTLIN Ryan Kotlin (La dataclass de Noah) Samuel Lambda Jackson (Jurassic Map) Mock Winslet (Typetanic) Emma Klaxon (Curry Potter) fantastiK!
  2. #loveStory @npatarino Clean Architecture • Independent of Frameworks and libraries

    • Independent of Database • Independent of UI • Domain centered • Testable
  3. #loveStory @npatarino Lists List<Movie> movies = repository.getMovies(); List<Movie> result =

    new ArrayList<>(); for (Movie element : movies) { if (element.getYear() > 2000) { result.add(element); } } System.out.println(movies);
  4. #loveStory @npatarino Lists Collection<Integer> collection = repository.getMovies(); Iterators.removeIf(collection.iterator(), new Predicate<Integer>()

    { @Overrid public boolean apply(Integer i) { return element.getYear() > 2000; } }); System.out.println(collection);
  5. #loveStory @npatarino Lists val movies: List<Movie> = repository.getMovies() val result

    = movies.filter { it.year > 200 } .sortedBy { it.title } .takeLast(5) .reversed() .slice(0..3) println(result)
  6. #loveStory @npatarino Movie class public class Movie { private String

    id, title; private List<Person> characters; private int year; public Movie(final String id, final String title, final List<Person> characters, final int year) { this.id = id; this.title = title; this.characters = characters; this.year = year; } }
  7. #loveStory @npatarino Movie class @Override public boolean equals(final Object o)

    { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Movie movie = (Movie) o; return year == movie.year && Objects.equals(id, movie.id) && Objects.equals(title, movie.title) && Objects.equals(characters, movie.characters); } @Override public int hashCode() { return Objects.hash(id, title, characters, year); }
  8. #loveStory @npatarino Movie class @Override public String toString() { return

    "Movie{" + "id='" + id + '\'' + ", title='" + title + '\'' + ", characters=" + characters + ", year=" + year + '}'; }
  9. #loveStory @npatarino Movie data class data class Movie(var id: String,

    var title: String, var characters: List<Person>, var year: Int)
  10. #loveStory @npatarino Copy objects val movie = Movie("Typetanic", 1997) val

    copy = movie.copy(title = "La dataclass de Noah")
  11. #loveStory @npatarino Copy objects val movie = Movie("Typetanic", 1997) val

    copy = movie.copy(title = "La dataclass de Noah", year = 2005, awards = 36, compositor = "Emma Klaxon", director = "Samuel Lambda Jackson")
  12. #loveStory @npatarino Mapping objects public class MovieMapper { public Movie

    map(final MovieEntity entity) { final int yearInt = Integer.parseInt(entity.getYear()); return new Movie(entity.getTitle(), yearInt); } }
  13. #loveStory @npatarino Mapping objects public class MovieRepositoryImpl implements MovieRepository {

    ... @Nullable @Override public Movie getMovieById(final String id) { final MovieEntity entity = localDatasource.getMovieById(id); final MovieMapper mapper = new MovieMapper(); if (entity != null) { return mapper.map(entity); } else { final MovieEntity movieEntity = networkDatasource.getMovieById(id); if (movieEntity != null) { return mapper.map(movieEntity); } } return null; } }
  14. #loveStory @npatarino Mapping objects fun MovieEntity.toDomain(): Movie = Movie(title!!, year!!)

    class MovieRepositoryImpl: MovieRepository { override fun getMovieById(id: String): Movie = localDatasource.getMovieById(id).toDomain() ?: networkDatasource.getMovieById(id).toDomain() }
  15. #loveStory @npatarino Mapping objects fun MovieEntity.toDomain(): Either<MappingError, Movie> = title?.let

    { year?.let { Right(Movie(title, year)) } ?: Left(MappingError) } ?: Left(MappingError)
  16. #loveStory @npatarino Error handling - Java public interface Repository {

    Movie getMovie(String id) } public void run() { try { final Movie movie = repository.getMovie("1"); } catch (Exception e) { System.out.println("Ouch! Error: " + e.getLocalizedMessage()); } }
  17. #loveStory @npatarino Error handling - Java public interface Repository {

    Movie getMovie(String id) throws IOException, NoSuchFieldException; } public void run() { try { final Movie movie = repository.getMovie("1"); } catch (NoSuchFieldException e) { System.out.println("Not movie found error: " + e.getLocalizedMessage()); } catch (IOException e) { System.out.println("Network error: " + e.getLocalizedMessage()); } }
  18. #loveStory @npatarino Error handling - Java public interface Repository {

    Movie getMovie(String id) throws NetworkException, NotMovieException; } public void run() { try { final Movie movie = repository.getMovie("1"); } catch (NotMovieException e) { System.out.println("Not movie found error: " + e.getLocalizedMessage()); } catch (NetworkException e) { System.out.println("Network error: " + e.getLocalizedMessage()); } }
  19. #loveStory @npatarino • Very expensive • Bad modelling • Should

    be exceptionals • Unchecked exceptions Problems with exceptions
  20. #loveStory @npatarino Enums public enum DatasourceError { MOVIE_NOT_FOUND(“Movie not found”),

    NETWORK_ERROR(“There is no internet connection”), FILESYSTEM_ERROR(“Unable to read the file”) private String error; Genre(final String error) { this.error = error; } private int getError() { return error; } }
  21. #loveStory @npatarino • Restricted hierarchy • Enum with vitamins ◦

    Several instances per class ◦ They carry states Sealed class
  22. #loveStory @npatarino Sealed class sealed class DataSourceError { class NotFound(val

    id: String) : DataSourceError() object NetworkError : DataSourceError() class FilesystemError(val file: File) : DataSourceError() }
  23. #loveStory @npatarino Either • Two values ◦ Left ◦ Right

    • Result in some languages (Swift) & libraries • Communicate error or success • Similar to Option or Try
  24. #loveStory @npatarino Either sealed class Either<out L, out R> {

    companion object } data class Left<out L>(val error: L) : Either<L, Nothing>() data class Right<out R>(val value: R) : Either<Nothing, R>()
  25. #loveStory @npatarino Either sealed class RepositoryError { object NoInternet :

    RepositoryError() object UnAuthorized : RepositoryError() object InvalidRequest : RepositoryError() object FilePermissionDenied : RepositoryError() }
  26. #loveStory @npatarino Either sealed class GetMovieError { class MovieNotFound(val id:

    String) : GetMovie() class InvalidData(val id: String) : GetMovie() }
  27. #loveStory @npatarino Composing sealed class sealed class GetMovieError { class

    RepositoryError2(val repositoryError: RepositoryError) : GetMovieError() class UseCaseError(val getMovieError: GetMovieError) : GetMovieError() }
  28. #loveStory @npatarino Either typealias EitherMovie = Either<GetMovieError, Movie> fun getMovie(id:

    String, movieRepository: MovieRepository) { val eitherMovie: EitherMovie = movieRepository.getMovieById(id) eitherMovie.fold({ handleError(it) }, { handleSuccess(it) }) } fun handleSuccess(movie: Movie) = println(movie)
  29. #loveStory @npatarino Pattern matching fun handleError(error: GetMovieError) = when (error)

    { is GetMovieError.RepositoryError2 -> when (error.repositoryError) { RepositoryError.NoInternet -> println("No internet connection") RepositoryError.UnAuthorized -> println("Token expired") } is GetMovieError.UseCaseError -> when (error.getMovieError) { is GetMovie.MovieNotFound -> println("Movie ${error.getMovieError.id}") is GetMovie.InvalidData -> println("Id(${error.getMovieError.id} invalid)") } }
  30. #loveStory @npatarino Use cases public class GetMovie { private MovieRepository

    repository; private String id; public GetMovie(final MovieRepository repository, final String id) { this.repository = repository; this.id = id; } public Movie execute() { return repository.getMovieById(id); } }
  31. #loveStory @npatarino Use cases fun getMovie(id: String, movieRepository: MovieRepository) =

    movieRepository.getMovieById(id) fun getMovies(personRepository: PersonRepository) = personRepository.getMovies() fun getPerson(id: String, personRepository: PersonRepository) = personRepository.getPerson(id) fun getPersons(personRepository: PersonRepository) = personRepository.getPersons()
  32. #loveStory @npatarino Execution UseCase<CreateMovieError, Movie>() .bg({ createMovie(movie) }) .map {

    it.toModel() } .ui({ it.fold({ error -> view.showError(error) }, { movie -> view.showMovie(movie) }) }) .run(useCaseExecutor)
  33. #loveStory @npatarino Execution interface UseCaseExecutor { fun <Error, Result> execute(background:

    () -> Either<Error, Result>, ui: (Either<Error, Result>) -> Unit, delayed: Long = 0): CancellationToken }
  34. #loveStory @npatarino Execution override fun <Error, Result> execute(background: () ->

    Either<Error, Result>, ui: (Either<Error, Result>) -> Unit, delayed: Long): CancellationToken = JobCancellationToken(launch { delay(delayed) ui(background()) })
  35. #loveStory @npatarino Execution override fun <Error, Result> execute(background: () ->

    Either<Error, Result>, ui: (Either<Error, Result>) -> Unit, delayed: Long): CancellationToken = JobCancellationToken(runBlocking { val job = launch { delay(delayed); ui(background()) } job.join(); job })
  36. #loveStory @npatarino Execution UseCase<CreateMovieError, Movie>() .bg({ createMovie(movie) }) .map {

    it.toModel() } .ui({ it.fold({ error -> view.showError(error) }, { movie -> view.showMovie(movie) }) }) .run(useCaseExecutor)
  37. #loveStory @npatarino La vida no se mide por las veces

    que compilas, sino por los momentos que te dejan sin refactor Hitch: Especialista en lambdas
  38. #loveStory @npatarino - You just tell me what you want,

    and I'm gonna be that for you - … You are functional - I could be that Ryan Kotlin - Crazy, Stupid, Code