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

When Clean Architecture met Kotlin: A love story (Vol. 2)

When Clean Architecture met Kotlin: A love story (Vol. 2)

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

May 23, 2018
Tweet

More Decks by Nicolás Patarino

Other Decks in Technology

Transcript

  1. When Clean Architecture Met Kotlin A love story (vol 2)

    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. Clean Architecture • Independent of Frameworks and libraries • Independent

    of Database • Independent of UI • Domain centered • Testable
  3. 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. 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. Lists List<Movie> movies = repository.getMovies(); List<Movie> result = movies.stream() .filter(movie

    -> movie.getYear() > 2000) .collect(Collectors.toList()); System.out.println(result) 8
  6. 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)
  7. Smart Cast & Check Type if (item instanceof Movie) {

    println( (Movie) (item.title)) } if (item is Movie) { println(item.title) }
  8. Type Inference Map<Integer, String> map = new HashMap<>(); map.put(1, "one");

    map.put(2, "two"); val map = mapOf(1 to "one", 2 to "two")
  9. 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; } }
  10. 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); }
  11. Movie class @Override public String toString() { return "Movie{" +

    "id='" + id + '\'' + ", title='" + title + '\'' + ", characters=" + characters + ", year=" + year + '}'; }
  12. Movie data class data class Movie(var id: String, var title:

    String, var characters: List<Person>, var year: Int)
  13. Mutability MovieManager movieManager = new MovieManager(); Movie movie = new

    Movie("Jurassic Map", 1993); movie.setYear(2017); movieManager.doSomething(movie); Log.d(movie.getYear());
  14. Immutability public void setYear(final String year) { this.year = year;

    } public Movie withYear(int year) { return new Movie(this.title, year); } MovieManager movieManager = new MovieManager(); Movie movie = new Movie("Jurassic Map", 1993); movie = movie.withYear(2017); movieManager.doSomething(movie); Log.d(movie.getYear());
  15. Immutability data class Movie(var title: String, var year: Int) data

    class Movie(val title: String, val year: Int)
  16. Copy objects val movie = Movie("Typetanic", 1997) val copy =

    movie.copy(title = "La dataclass de Noah")
  17. 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")
  18. Mapping objects public class MovieMapper { public Movie map(final MovieEntity

    entity) { final int yearInt = Integer.parseInt(entity.getYear()); return new Movie(entity.getTitle(), yearInt); } }
  19. 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; } }
  20. Mapping objects fun MovieEntity.toDomain(): Movie = Movie(title, year) ... class

    MovieRepositoryData: MovieRepository { operator override fun get(id: String): Movie = localDatasource.getMovieById(id).recover { networkDatasource.getMovieById(id) }.toDomain() }
  21. 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; } }
  22. Operators fun MovieEntity.toDomain(): Movie = Movie(title, year) class MovieRepositoryData: MovieRepository

    { operator override fun get(id: String): Movie = localDatasource.getMovieById(id).recover { networkDatasource.getMovieById(id) }.toDomain() } }
  23. Operators fun MovieEntity.toDomain(): Movie = Movie(title, year) class MovieRepositoryData: MovieRepository

    { operator override fun get(id: String): Movie = localDatasource.getMovieById(id).recover { networkDatasource.getMovieById(id) }.toDomain() } val movie = movieRepository["123456"]
  24. Operator overloading a + b -> a.plus(b) a - b

    -> a.minus(b) a * b -> a.times(b) a[i] -> a.get(i) a[i] = b -> a.set(i, b) a() -> a.invoke() a(i) -> a.invoke(i) a += b -> a.plusAssign(b) a == b -> a?.equals(b) ?: (b === null) a > b -> a.compareTo(b) > 0 a >= b -> a.compareTo(b) >= 0
  25. Mapping objects fun MovieEntity.toDomain(): Either<MappingError, Movie> = title?.let { year?.let

    { Right(Movie(title, year)) } ?: Left(MappingError) } ?: Left(MappingError)
  26. Calcular el coste total (€) de un listado de tareas

    Petición devuelve List<TaskEntity> Parsear a List<Task> Calcular el tiempo de cada Task Calcular el coste del tiempo de cada Task Sumar todos los costes Functional Programming
  27. Calculate tasks cost public Integer fullCost(List<TaskEntity> tasks) { Integer sum

    = 0; for (TaskEntity taskEntity : tasks) { Task task = toDomain(taskEntity); int timeSpent = task.getTimeSpent(); int timePriced = getTimePriced(timeSpent); sum += timePriced; } return sum; }
  28. Calculate tasks cost Reduce ( AppyToAll ( ApplyToAll( ApplyToAll( tasksEntity,

    toDomain ), spentTime ), spentTimeInEuros ), Sum )
  29. Calculate tasks cost Reduce ( Map ( Map ( Map

    ( tasksEntity, toDomain ), spentTime ), spentTimeInEuros ), Sum )
  30. Calculate tasks cost Reduce ( Map ( Map ( Map

    ( tasksEntity, toDomain ), spentTime ), spentTimeInEuros ), Sum )
  31. Calculate tasks cost Reduce ( Map ( Map ( listToDomain

    ( tasksEntity ), spentTime ), spentTimeInEuros ), Sum )
  32. Calculate tasks cost Reduce ( Map ( Map ( listToDomain

    (tasksEntity), spentTime ), spentTimeInEuros ), Sum )
  33. Calculate tasks cost Reduce ( Map ( Map ( listToDomain

    (tasksEntity), spentTime ), spentTimeInEuros ), Sum )
  34. Calculate tasks cost Reduce ( Map ( listSpentTime ( listToDomain

    (tasksEntity) ), spentTimeInEuros ), Sum )
  35. Calculate tasks cost Reduce ( Map ( listSpentTime ( listToDomain

    (tasksEntity) ), spentTimeInEuros ), Sum )
  36. Calculate tasks cost private fun List<TaskEntity>.listToDomain(): List<Task> = map {

    it.toDomain() } private fun List<Task>.listSpentTime(): List<Int> = map { it.timeSpent } private fun List<Int>.listTimeInEuros(): List<Int> = map { getTimeInEuros(it) }
  37. Calculate tasks cost public Integer fullCost(List<TaskEntity> tasks) { Integer sum

    = 0; for (TaskEntity taskEntity : tasks) { Task task = toDomain(taskEntity); int timeSpent = task.getTimeSpent(); int timePriced = getTimePriced(timeSpent); sum += timePriced; } return sum; }
  38. 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()); } }
  39. 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()); } }
  40. 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()); } }
  41. • Very expensive • Bad modelling • Should be exceptionals

    • Let system inconsistent • Unchecked exceptions Problems with exceptions
  42. 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; } }
  43. • Restricted hierarchy • Enum with vitamins ◦ Several instances

    per class ◦ They carry states Sealed class
  44. Sealed class sealed class DataSourceError { class NotFound(val id: String)

    : DataSourceError() object NetworkError : DataSourceError() class FilesystemError(val file: File) : DataSourceError() }
  45. Either • Two values ◦ Left ◦ Right • Result

    in some languages (Swift) & libraries • Communicate error or success • Similar to Option or Try
  46. 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>() fun <C> fold(left: (L) -> C, right: (R) -> C): C
  47. Either typealias ResultMovie = Either<RepositoryError, Movie> fun getMovie(id: String, movieRepository:

    MovieRepository) { val resultMovie: ResultMovie = movieRepository.getMovieById(id) resultMovie.fold({ handleError(it) }, { handleSuccess(it) }) } fun handleSuccess(movie: Movie) = println(movie)
  48. Pattern matching fun handleError(error: RepositoryError) = when (error) { RepositoryError.NoInternet

    -> println("No internet connection") RepositoryError.UnAuthorized -> println("Token expired") }
  49. 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); } }
  50. 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()
  51. Execution useCase<Error, Result> { bg = tasks(tasksRepository) delay = 5000

    ui = fold({ handleError() }, { handleSuccess(it) }) }.run(useCaseExecutor)
  52. Execution override fun <Error, Result> execute(background: () -> Either<Error, Result>,

    ui: (Either<Error, Result>) -> Unit, delayed: Long): CancellationToken = JobCancellationToken(launch { delay(delayed) ui(background()) })
  53. 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 })
  54. Execution useCase<Error, Result> { bg = tasks(tasksRepository) delay = 5000

    ui = fold({ handleError() }, { handleSuccess(it) }) }.run(useCaseExecutor)
  55. La vida no se mide por las veces que compilas,

    sino por los momentos que te dejan sin refactor Hitch: Especialista en lambdas
  56. - 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