SlideShare a Scribd company logo
SOLID principles in practice:
the clean architecture
Fabio Collini
@fabioCollini
linkedin.com/in/fabiocollini
github.com/fabioCollini
medium.com/@fabioCollini
codingjam.it
Android programmazione avanzata
Android Developers Italia
Ego slide
entitiesentities
Clean
architecture
S.O.L.I.D.
principles
SOLID principles in practice: the Clean Architecture
./gradlew run —args=Firenze
Firenze (IT) - 25º min 4º max 29º
./gradlew run --args=Springfield
Springfield (US) - 8º min 4º max 27º
./gradlew run --args=sdfdsffdfdfds
No city found
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
Coroutines
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
Dependency Injection
class WeatherUseCaseTest {
val cityRetriever: CityRetriever = mockk()
val repository: TemperatureRepository = mockk()
val useCase = WeatherUseCase(cityRetriever, repository)
@Test
fun retrieveCityData() {
every { cityRetriever.findCity("Firenze") } returns
listOf(City(CITY_ID, "Firenze", "IT"))
coEvery { repository.getTemperature(CITY_ID) } returns
Temperature(10, 8, 20)
val cityData = runBlocking { useCase.getCityData("Firenze") }
assert(cityData).isEqualTo("Firenze (IT) - 10º min 8º max 20º")
}1
}2
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
main
api
domain
weather
city
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
Robert C. Martin Copyright (c) 2000 by Robert C. Martin. All Rights Reserved.
www.objectmentor.com 1
Design Principles and
Design Patterns
Robert C. Martin
www.objectmentor.com
What is software architecture? The answer is multitiered. At the highest level, there
are the architecture patterns that define the overall shape and structure of software
applications1
. Down a level is the architecture that is specifically related to the pur-
pose of the software application. Yet another level down resides the architecture of
the modules and their interconnections. This is the domain of design patterns2
, pack-
akges, components, and classes. It is this level that we will concern ourselves with in
this chapter.
Our scope in this chapter is quite limitted. There is much more to be said about the
principles and patterns that are exposed here. Interested readers are referred to
[Martin99].
Architecture and Dependencies
What goes wrong with software? The design of many software applications begins as
a vital image in the minds of its designers. At this stage it is clean, elegant, and com-
pelling. It has a simple beauty that makes the designers and implementers itch to see it
working. Some of these applications manage to maintain this purity of design through
the initial development and into the first release.
But then something begins to happen. The software starts to rot. At first it isn’t so
bad. An ugly wart here, a clumsy hack there, but the beauty of the design still shows
through. Yet, over time as the rotting continues, the ugly festering sores and boils
accumulate until they dominate the design of the application. The program becomes a
festering mass of code that the developers find increasingly hard to maintain. Eventu-
1. [Shaw96]
2. [GOF96]
Copyright (c) 2000 by Robert C. Martin. All Rights Reserved.
S
O
L
I
D
Single responsibility
“A class should have one, and only one,
reason to change”
api
domain
weather
city
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
main
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
Open closed
“You should be able to extend a classes
behavior, without modifying it”
interface
impl1 impl2
interface
impl1 impl2 impl3
Liskov substitution
“Derived classes must be substitutable
for their base classes”
open class Rectangle(
open var width: Int,
open var height: Int
) {
fun area() = width * height
}
class Square(size: Int) : Rectangle(size, size) {
override var width: Int = size
set(value) {
field = value
if (height != value)
height = value
}
override var height: Int = size
set(value) {
field = value
if (width != value)
width = value
}
}
var rectangle = Rectangle(2, 3)
var initialArea = rectangle.area()
rectangle.width *= 2
println(initialArea * 2 == rectangle.area())
rectangle = Square(2)
initialArea = rectangle.area()
rectangle.width *= 2
println(initialArea * 2 == rectangle.area())
Interface segregation
“Make fine grained interfaces
that are client specific”
Dependency inversion
Dependency injection?
Inversion of control?
Dependency inversion
“Depend on abstractions,
not on concretions”
main
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
api
domain
weather
city
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
Dependency inversion
“High level modules should not depend
upon low level modules. Both should
depend upon abstractions”
main
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
api
domain
weather
city
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
domain city
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
domain city
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
domain city
class WeatherUseCase
interface CityRetriever class JsonCityRetriever : CityRetriever
domain
city
class WeatherUseCase interface CityRetriever
class JsonCityRetriever : CityRetriever
domain city
class WeatherUseCase
interface CityRetriever class JsonCityRetriever : CityRetriever
domain
city
class WeatherUseCase interface CityRetriever
class JsonCityRetriever : CityRetriever
“I declare an interface just
to implement dependency inversion or
when there are multiple implementations”
Anonymous at Droidcon London
“Declare an interface only
to implement a S.O.L.I.D. principle”
main
suspend fun main(args: Array<String>) {
val api = RetrofitFactory.createService<WeatherApi>()
val weatherRepository = OpenWeatherTemperatureRepository(api)
val cityRetriever = JsonCityRetriever()
val useCase = WeatherUseCase(cityRetriever, weatherRepository)
val result = useCase.getCityData(args[0])
println(result)
}O
api
domain
weather
city
interface WeatherApi {
@GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper>
@GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric")
fun forecast(@Query("id") id: Int): Deferred<Forecast>
}Z
class OpenWeatherTemperatureRepository(
private val api: WeatherApi
) : TemperatureRepository {
override suspend fun getTemperature(cityId: Int): Temperature {
val forecastDeferred = api.forecast(cityId)
val weather = api.currentWeather(cityId).await()
val temperatures = forecastDeferred.await().list.map { it.main }
return Temperature(
weather.main.temp.toInt(),
temperatures.map { it.temp_min }.min()?.toInt(),
temperatures.map { it.temp_max }.max()?.toInt()
)D
}E
}F
interface TemperatureRepository {
suspend fun getTemperature(cityId: Int): Temperature
}7
class WeatherUseCase(
private val cityRetriever: CityRetriever,
private val repository: TemperatureRepository) {
suspend fun getCityData(s: String): String {
return try {
val cities = cityRetriever.findCity(s)
if (cities.isEmpty()) {
"No city found"
} else {
val city = cities.first()
val temperature = repository.getTemperature(city.id)
"$city - $temperature"
}0
} catch (e: Exception) {
"Error retrieving data: ${e.message}"
}1
}2
}3
interface CityRetriever {
fun findCity(name: String): List<City>
}6
class JsonCityRetriever : CityRetriever {
private val allCities by lazy {
val text = javaClass.getResource("/cityList.json").readText()
Gson().fromJson<List<CityJson>>(text)
}A
override fun findCity(name: String): List<City> {
return allCities
.filter { it.name == name }
.map { City(it.id, it.name, it.country) }
.toList()
}B
}C
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
city
entities
main
weather
api
domain
repository
entities
UI
data source
domain
repository
entities
UI
data source
domain
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Prosentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Framework
independence
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Architecture
Vs
code conventions
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
SOLID principles in practice: the Clean Architecture
TDDentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Consentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
More codeentitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Architecture Vs
code conventions
TDD
Pros
Cons
More code
Framework
independence
entitiesentitiesentitiesentities
domain
repository
UI
data source
presenter
Links
Demo Project
github.com/fabioCollini/CleanWeather
2000 - Robert C. Martin - Design Principles and Design Patterns
www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/
materials_adicionals/principles_and_patterns.pdf
2005 - Robert C. Martin - The Principles of OOD
butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
2012 - Robert C. Martin - The Clean Architecture
8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html
THANKS
FOR YOUR
ATTENTION
QUESTIONS?
@fabioCollini
Ad

More Related Content

What's hot (20)

Local storage
Local storageLocal storage
Local storage
Adam Crabtree
 
Deploying a 3 tier application using docker
Deploying a 3 tier application using dockerDeploying a 3 tier application using docker
Deploying a 3 tier application using docker
parth2094
 
Whitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applicationsWhitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applications
Yura Nosenko
 
Migrating to Java 11
Migrating to Java 11Migrating to Java 11
Migrating to Java 11
Arto Santala
 
HƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬP
HƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬPHƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬP
HƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬP
dvms
 
Versioning avec Git
Versioning avec GitVersioning avec Git
Versioning avec Git
Jean-Baptiste Vigneron
 
Continuous Integration
Continuous IntegrationContinuous Integration
Continuous Integration
drluckyspin
 
Flutter overview - advantages & disadvantages for business
Flutter overview - advantages & disadvantages for businessFlutter overview - advantages & disadvantages for business
Flutter overview - advantages & disadvantages for business
Bartosz Kosarzycki
 
Custom Controls in ASP.net
Custom Controls in ASP.netCustom Controls in ASP.net
Custom Controls in ASP.net
kunj desai
 
Angular 2.0
Angular  2.0Angular  2.0
Angular 2.0
Mallikarjuna G D
 
Support distributed computing and caching avec hazelcast
Support distributed computing and caching avec hazelcastSupport distributed computing and caching avec hazelcast
Support distributed computing and caching avec hazelcast
ENSET, Université Hassan II Casablanca
 
Spring cloud config manage configuration
Spring cloud config manage configurationSpring cloud config manage configuration
Spring cloud config manage configuration
Janani Velmurugan
 
Git internals
Git internalsGit internals
Git internals
Hyderabad Scalability Meetup
 
Mejores prácticas desarrollo de base de datos
Mejores prácticas desarrollo de base de datos Mejores prácticas desarrollo de base de datos
Mejores prácticas desarrollo de base de datos
Eduardo Castro
 
Immutable Infrastructure with Packer Ansible and Terraform
Immutable Infrastructure with Packer Ansible and TerraformImmutable Infrastructure with Packer Ansible and Terraform
Immutable Infrastructure with Packer Ansible and Terraform
Michael Peacock
 
GitHub Basics - Derek Bable
GitHub Basics - Derek BableGitHub Basics - Derek Bable
GitHub Basics - Derek Bable
"FENG "GEORGE"" YU
 
Lập trình web - HTML cơ bản
Lập trình web - HTML cơ bảnLập trình web - HTML cơ bản
Lập trình web - HTML cơ bản
Nhóc Nhóc
 
Git Tutorial I
Git Tutorial IGit Tutorial I
Git Tutorial I
Jim Yeh
 
Source control
Source controlSource control
Source control
Sachithra Gayan
 
Git real slides
Git real slidesGit real slides
Git real slides
Lucas Couto
 
Deploying a 3 tier application using docker
Deploying a 3 tier application using dockerDeploying a 3 tier application using docker
Deploying a 3 tier application using docker
parth2094
 
Whitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applicationsWhitebox testing of Spring Boot applications
Whitebox testing of Spring Boot applications
Yura Nosenko
 
Migrating to Java 11
Migrating to Java 11Migrating to Java 11
Migrating to Java 11
Arto Santala
 
HƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬP
HƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬPHƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬP
HƯỚNG DẪN SỬ DỤNG PHẦN MỀM SUBVERSION (SVN) TOÀN TẬP
dvms
 
Continuous Integration
Continuous IntegrationContinuous Integration
Continuous Integration
drluckyspin
 
Flutter overview - advantages & disadvantages for business
Flutter overview - advantages & disadvantages for businessFlutter overview - advantages & disadvantages for business
Flutter overview - advantages & disadvantages for business
Bartosz Kosarzycki
 
Custom Controls in ASP.net
Custom Controls in ASP.netCustom Controls in ASP.net
Custom Controls in ASP.net
kunj desai
 
Spring cloud config manage configuration
Spring cloud config manage configurationSpring cloud config manage configuration
Spring cloud config manage configuration
Janani Velmurugan
 
Mejores prácticas desarrollo de base de datos
Mejores prácticas desarrollo de base de datos Mejores prácticas desarrollo de base de datos
Mejores prácticas desarrollo de base de datos
Eduardo Castro
 
Immutable Infrastructure with Packer Ansible and Terraform
Immutable Infrastructure with Packer Ansible and TerraformImmutable Infrastructure with Packer Ansible and Terraform
Immutable Infrastructure with Packer Ansible and Terraform
Michael Peacock
 
Lập trình web - HTML cơ bản
Lập trình web - HTML cơ bảnLập trình web - HTML cơ bản
Lập trình web - HTML cơ bản
Nhóc Nhóc
 
Git Tutorial I
Git Tutorial IGit Tutorial I
Git Tutorial I
Jim Yeh
 

Similar to SOLID principles in practice: the Clean Architecture (20)

SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila RomagnaSOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
Fabio Collini
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
Fabio Collini
 
K is for Kotlin
K is for KotlinK is for Kotlin
K is for Kotlin
TechMagic
 
JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?
PROIDEA
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
Fabio Collini
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Mario Fusco
 
Kotlin, why?
Kotlin, why?Kotlin, why?
Kotlin, why?
Paweł Byszewski
 
2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js
Noritada Shimizu
 
ADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developersADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developers
Bartosz Kosarzycki
 
Kotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsKotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projects
Bartosz Kosarzycki
 
Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016
Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016
Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016
STX Next
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Codemotion
 
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Ontico
 
Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++Using the Windows 8 Runtime from C++
Using the Windows 8 Runtime from C++
Microsoft Developer Network (MSDN) - Belgium and Luxembourg
 
Scala by Luc Duponcheel
Scala by Luc DuponcheelScala by Luc Duponcheel
Scala by Luc Duponcheel
Stephan Janssen
 
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Codemotion
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
Nelson Glauber Leal
 
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and JasmineRails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Raimonds Simanovskis
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
Dmitry Sheiko
 
Дмитрий Верескун «Синтаксический сахар C#»
Дмитрий Верескун «Синтаксический сахар C#»Дмитрий Верескун «Синтаксический сахар C#»
Дмитрий Верескун «Синтаксический сахар C#»
SpbDotNet Community
 
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila RomagnaSOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
SOLID principles in practice: the Clean Architecture - Devfest Emila Romagna
Fabio Collini
 
Solid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon ItalySolid principles in practice the clean architecture - Droidcon Italy
Solid principles in practice the clean architecture - Droidcon Italy
Fabio Collini
 
K is for Kotlin
K is for KotlinK is for Kotlin
K is for Kotlin
TechMagic
 
JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?
PROIDEA
 
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf MilanFrom Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
From Java to Kotlin beyond alt+shift+cmd+k - Kotlin Community Conf Milan
Fabio Collini
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Mario Fusco
 
2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js2016 gunma.web games-and-asm.js
2016 gunma.web games-and-asm.js
Noritada Shimizu
 
ADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developersADG Poznań - Kotlin for Android developers
ADG Poznań - Kotlin for Android developers
Bartosz Kosarzycki
 
Kotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projectsKotlin Developer Starter in Android projects
Kotlin Developer Starter in Android projects
Bartosz Kosarzycki
 
Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016
Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016
Kotlin Developer Starter in Android - STX Next Lightning Talks - Feb 12, 2016
STX Next
 
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...Laziness, trampolines, monoids and other functional amenities: this is not yo...
Laziness, trampolines, monoids and other functional amenities: this is not yo...
Codemotion
 
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Kotlin Perfomance on Android / Александр Смирнов (Splyt)
Ontico
 
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Kotlin for Android Developers - Victor Kropp - Codemotion Rome 2018
Codemotion
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
Nelson Glauber Leal
 
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and JasmineRails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Rails-like JavaScript Using CoffeeScript, Backbone.js and Jasmine
Raimonds Simanovskis
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
Dmitry Sheiko
 
Дмитрий Верескун «Синтаксический сахар C#»
Дмитрий Верескун «Синтаксический сахар C#»Дмитрий Верескун «Синтаксический сахар C#»
Дмитрий Верескун «Синтаксический сахар C#»
SpbDotNet Community
 
Ad

More from Fabio Collini (20)

Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
Fabio Collini
 
Using hilt in a modularized project
Using hilt in a modularized projectUsing hilt in a modularized project
Using hilt in a modularized project
Fabio Collini
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
Fabio Collini
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community conf
Fabio Collini
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Fabio Collini
 
Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture project
Fabio Collini
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Fabio Collini
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018
Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+k
Fabio Collini
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UK
Fabio Collini
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
Fabio Collini
 
Testing Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJavaTesting Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJava
Fabio Collini
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUK
Fabio Collini
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
Fabio Collini
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG Firenze
Fabio Collini
 
Testable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMTestable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVM
Fabio Collini
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJava
Fabio Collini
 
Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015
Fabio Collini
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014
Fabio Collini
 
Architectures in the compose world
Architectures in the compose worldArchitectures in the compose world
Architectures in the compose world
Fabio Collini
 
Using hilt in a modularized project
Using hilt in a modularized projectUsing hilt in a modularized project
Using hilt in a modularized project
Fabio Collini
 
Managing parallelism using coroutines
Managing parallelism using coroutinesManaging parallelism using coroutines
Managing parallelism using coroutines
Fabio Collini
 
Kotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community confKotlin Delegates in practice - Kotlin community conf
Kotlin Delegates in practice - Kotlin community conf
Fabio Collini
 
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere StockholmKotlin delegates in practice - Kotlin Everywhere Stockholm
Kotlin delegates in practice - Kotlin Everywhere Stockholm
Fabio Collini
 
Using Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture projectUsing Dagger in a Clean Architecture project
Using Dagger in a Clean Architecture project
Fabio Collini
 
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night TurinAsync code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Async code on kotlin: rx java or/and coroutines - Kotlin Night Turin
Fabio Collini
 
Recap Google I/O 2018
Recap Google I/O 2018Recap Google I/O 2018
Recap Google I/O 2018
Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italyFrom java to kotlin beyond alt+shift+cmd+k - Droidcon italy
From java to kotlin beyond alt+shift+cmd+k - Droidcon italy
Fabio Collini
 
From java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+kFrom java to kotlin beyond alt+shift+cmd+k
From java to kotlin beyond alt+shift+cmd+k
Fabio Collini
 
Testing Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UKTesting Android apps based on Dagger and RxJava Droidcon UK
Testing Android apps based on Dagger and RxJava Droidcon UK
Fabio Collini
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
Fabio Collini
 
Testing Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJavaTesting Android apps based on Dagger and RxJava
Testing Android apps based on Dagger and RxJava
Fabio Collini
 
Android Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUKAndroid Data Binding in action using MVVM pattern - droidconUK
Android Data Binding in action using MVVM pattern - droidconUK
Fabio Collini
 
Data Binding in Action using MVVM pattern
Data Binding in Action using MVVM patternData Binding in Action using MVVM pattern
Data Binding in Action using MVVM pattern
Fabio Collini
 
Android Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG FirenzeAndroid Wear CodeLab - GDG Firenze
Android Wear CodeLab - GDG Firenze
Fabio Collini
 
Testable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVMTestable Android Apps using data binding and MVVM
Testable Android Apps using data binding and MVVM
Fabio Collini
 
Introduction to Retrofit and RxJava
Introduction to Retrofit and RxJavaIntroduction to Retrofit and RxJava
Introduction to Retrofit and RxJava
Fabio Collini
 
Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015Testable Android Apps DroidCon Italy 2015
Testable Android Apps DroidCon Italy 2015
Fabio Collini
 
Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014Clean android code - Droidcon Italiy 2014
Clean android code - Droidcon Italiy 2014
Fabio Collini
 
Ad

Recently uploaded (20)

Get & Download Wondershare Filmora Crack Latest [2025]
Get & Download Wondershare Filmora Crack Latest [2025]Get & Download Wondershare Filmora Crack Latest [2025]
Get & Download Wondershare Filmora Crack Latest [2025]
saniaaftab72555
 
Who Watches the Watchmen (SciFiDevCon 2025)
Who Watches the Watchmen (SciFiDevCon 2025)Who Watches the Watchmen (SciFiDevCon 2025)
Who Watches the Watchmen (SciFiDevCon 2025)
Allon Mureinik
 
LEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRY
LEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRYLEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRY
LEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRY
NidaFarooq10
 
PDF Reader Pro Crack Latest Version FREE Download 2025
PDF Reader Pro Crack Latest Version FREE Download 2025PDF Reader Pro Crack Latest Version FREE Download 2025
PDF Reader Pro Crack Latest Version FREE Download 2025
mu394968
 
TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...
TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...
TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...
Andre Hora
 
Expand your AI adoption with AgentExchange
Expand your AI adoption with AgentExchangeExpand your AI adoption with AgentExchange
Expand your AI adoption with AgentExchange
Fexle Services Pvt. Ltd.
 
How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?
How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?
How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?
steaveroggers
 
Not So Common Memory Leaks in Java Webinar
Not So Common Memory Leaks in Java WebinarNot So Common Memory Leaks in Java Webinar
Not So Common Memory Leaks in Java Webinar
Tier1 app
 
Pixologic ZBrush Crack Plus Activation Key [Latest 2025] New Version
Pixologic ZBrush Crack Plus Activation Key [Latest 2025] New VersionPixologic ZBrush Crack Plus Activation Key [Latest 2025] New Version
Pixologic ZBrush Crack Plus Activation Key [Latest 2025] New Version
saimabibi60507
 
Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.
Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.
Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.
Dele Amefo
 
Secure Test Infrastructure: The Backbone of Trustworthy Software Development
Secure Test Infrastructure: The Backbone of Trustworthy Software DevelopmentSecure Test Infrastructure: The Backbone of Trustworthy Software Development
Secure Test Infrastructure: The Backbone of Trustworthy Software Development
Shubham Joshi
 
Maxon CINEMA 4D 2025 Crack FREE Download LINK
Maxon CINEMA 4D 2025 Crack FREE Download LINKMaxon CINEMA 4D 2025 Crack FREE Download LINK
Maxon CINEMA 4D 2025 Crack FREE Download LINK
younisnoman75
 
Landscape of Requirements Engineering for/by AI through Literature Review
Landscape of Requirements Engineering for/by AI through Literature ReviewLandscape of Requirements Engineering for/by AI through Literature Review
Landscape of Requirements Engineering for/by AI through Literature Review
Hironori Washizaki
 
Automation Techniques in RPA - UiPath Certificate
Automation Techniques in RPA - UiPath CertificateAutomation Techniques in RPA - UiPath Certificate
Automation Techniques in RPA - UiPath Certificate
VICTOR MAESTRE RAMIREZ
 
Adobe Lightroom Classic Crack FREE Latest link 2025
Adobe Lightroom Classic Crack FREE Latest link 2025Adobe Lightroom Classic Crack FREE Latest link 2025
Adobe Lightroom Classic Crack FREE Latest link 2025
kashifyounis067
 
How to Optimize Your AWS Environment for Improved Cloud Performance
How to Optimize Your AWS Environment for Improved Cloud PerformanceHow to Optimize Your AWS Environment for Improved Cloud Performance
How to Optimize Your AWS Environment for Improved Cloud Performance
ThousandEyes
 
Adobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage Dashboards
Adobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage DashboardsAdobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage Dashboards
Adobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage Dashboards
BradBedford3
 
Exploring Code Comprehension in Scientific Programming: Preliminary Insight...
Exploring Code Comprehension  in Scientific Programming:  Preliminary Insight...Exploring Code Comprehension  in Scientific Programming:  Preliminary Insight...
Exploring Code Comprehension in Scientific Programming: Preliminary Insight...
University of Hawai‘i at Mānoa
 
Explaining GitHub Actions Failures with Large Language Models Challenges, In...
Explaining GitHub Actions Failures with Large Language Models Challenges, In...Explaining GitHub Actions Failures with Large Language Models Challenges, In...
Explaining GitHub Actions Failures with Large Language Models Challenges, In...
ssuserb14185
 
Designing AI-Powered APIs on Azure: Best Practices& Considerations
Designing AI-Powered APIs on Azure: Best Practices& ConsiderationsDesigning AI-Powered APIs on Azure: Best Practices& Considerations
Designing AI-Powered APIs on Azure: Best Practices& Considerations
Dinusha Kumarasiri
 
Get & Download Wondershare Filmora Crack Latest [2025]
Get & Download Wondershare Filmora Crack Latest [2025]Get & Download Wondershare Filmora Crack Latest [2025]
Get & Download Wondershare Filmora Crack Latest [2025]
saniaaftab72555
 
Who Watches the Watchmen (SciFiDevCon 2025)
Who Watches the Watchmen (SciFiDevCon 2025)Who Watches the Watchmen (SciFiDevCon 2025)
Who Watches the Watchmen (SciFiDevCon 2025)
Allon Mureinik
 
LEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRY
LEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRYLEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRY
LEARN SEO AND INCREASE YOUR KNOWLDGE IN SOFTWARE INDUSTRY
NidaFarooq10
 
PDF Reader Pro Crack Latest Version FREE Download 2025
PDF Reader Pro Crack Latest Version FREE Download 2025PDF Reader Pro Crack Latest Version FREE Download 2025
PDF Reader Pro Crack Latest Version FREE Download 2025
mu394968
 
TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...
TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...
TestMigrationsInPy: A Dataset of Test Migrations from Unittest to Pytest (MSR...
Andre Hora
 
Expand your AI adoption with AgentExchange
Expand your AI adoption with AgentExchangeExpand your AI adoption with AgentExchange
Expand your AI adoption with AgentExchange
Fexle Services Pvt. Ltd.
 
How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?
How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?
How to Batch Export Lotus Notes NSF Emails to Outlook PST Easily?
steaveroggers
 
Not So Common Memory Leaks in Java Webinar
Not So Common Memory Leaks in Java WebinarNot So Common Memory Leaks in Java Webinar
Not So Common Memory Leaks in Java Webinar
Tier1 app
 
Pixologic ZBrush Crack Plus Activation Key [Latest 2025] New Version
Pixologic ZBrush Crack Plus Activation Key [Latest 2025] New VersionPixologic ZBrush Crack Plus Activation Key [Latest 2025] New Version
Pixologic ZBrush Crack Plus Activation Key [Latest 2025] New Version
saimabibi60507
 
Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.
Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.
Salesforce Data Cloud- Hyperscale data platform, built for Salesforce.
Dele Amefo
 
Secure Test Infrastructure: The Backbone of Trustworthy Software Development
Secure Test Infrastructure: The Backbone of Trustworthy Software DevelopmentSecure Test Infrastructure: The Backbone of Trustworthy Software Development
Secure Test Infrastructure: The Backbone of Trustworthy Software Development
Shubham Joshi
 
Maxon CINEMA 4D 2025 Crack FREE Download LINK
Maxon CINEMA 4D 2025 Crack FREE Download LINKMaxon CINEMA 4D 2025 Crack FREE Download LINK
Maxon CINEMA 4D 2025 Crack FREE Download LINK
younisnoman75
 
Landscape of Requirements Engineering for/by AI through Literature Review
Landscape of Requirements Engineering for/by AI through Literature ReviewLandscape of Requirements Engineering for/by AI through Literature Review
Landscape of Requirements Engineering for/by AI through Literature Review
Hironori Washizaki
 
Automation Techniques in RPA - UiPath Certificate
Automation Techniques in RPA - UiPath CertificateAutomation Techniques in RPA - UiPath Certificate
Automation Techniques in RPA - UiPath Certificate
VICTOR MAESTRE RAMIREZ
 
Adobe Lightroom Classic Crack FREE Latest link 2025
Adobe Lightroom Classic Crack FREE Latest link 2025Adobe Lightroom Classic Crack FREE Latest link 2025
Adobe Lightroom Classic Crack FREE Latest link 2025
kashifyounis067
 
How to Optimize Your AWS Environment for Improved Cloud Performance
How to Optimize Your AWS Environment for Improved Cloud PerformanceHow to Optimize Your AWS Environment for Improved Cloud Performance
How to Optimize Your AWS Environment for Improved Cloud Performance
ThousandEyes
 
Adobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage Dashboards
Adobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage DashboardsAdobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage Dashboards
Adobe Marketo Engage Champion Deep Dive - SFDC CRM Synch V2 & Usage Dashboards
BradBedford3
 
Exploring Code Comprehension in Scientific Programming: Preliminary Insight...
Exploring Code Comprehension  in Scientific Programming:  Preliminary Insight...Exploring Code Comprehension  in Scientific Programming:  Preliminary Insight...
Exploring Code Comprehension in Scientific Programming: Preliminary Insight...
University of Hawai‘i at Mānoa
 
Explaining GitHub Actions Failures with Large Language Models Challenges, In...
Explaining GitHub Actions Failures with Large Language Models Challenges, In...Explaining GitHub Actions Failures with Large Language Models Challenges, In...
Explaining GitHub Actions Failures with Large Language Models Challenges, In...
ssuserb14185
 
Designing AI-Powered APIs on Azure: Best Practices& Considerations
Designing AI-Powered APIs on Azure: Best Practices& ConsiderationsDesigning AI-Powered APIs on Azure: Best Practices& Considerations
Designing AI-Powered APIs on Azure: Best Practices& Considerations
Dinusha Kumarasiri
 

SOLID principles in practice: the Clean Architecture

  • 1. SOLID principles in practice: the clean architecture Fabio Collini
  • 5. ./gradlew run —args=Firenze Firenze (IT) - 25º min 4º max 29º ./gradlew run --args=Springfield Springfield (US) - 8º min 4º max 27º ./gradlew run --args=sdfdsffdfdfds No city found
  • 6. class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 7. class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 8. class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 Coroutines
  • 9. class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 Dependency Injection
  • 10. class WeatherUseCaseTest { val cityRetriever: CityRetriever = mockk() val repository: TemperatureRepository = mockk() val useCase = WeatherUseCase(cityRetriever, repository) @Test fun retrieveCityData() { every { cityRetriever.findCity("Firenze") } returns listOf(City(CITY_ID, "Firenze", "IT")) coEvery { repository.getTemperature(CITY_ID) } returns Temperature(10, 8, 20) val cityData = runBlocking { useCase.getCityData("Firenze") } assert(cityData).isEqualTo("Firenze (IT) - 10º min 8º max 20º") }1 }2
  • 11. interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 12. interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 13. class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 14. class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 15. class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 16. interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 17. class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 18. class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F
  • 19. interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z
  • 20. interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z
  • 21. interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O
  • 22. suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O
  • 23. main api domain weather city interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O
  • 24. Robert C. Martin Copyright (c) 2000 by Robert C. Martin. All Rights Reserved. www.objectmentor.com 1 Design Principles and Design Patterns Robert C. Martin www.objectmentor.com What is software architecture? The answer is multitiered. At the highest level, there are the architecture patterns that define the overall shape and structure of software applications1 . Down a level is the architecture that is specifically related to the pur- pose of the software application. Yet another level down resides the architecture of the modules and their interconnections. This is the domain of design patterns2 , pack- akges, components, and classes. It is this level that we will concern ourselves with in this chapter. Our scope in this chapter is quite limitted. There is much more to be said about the principles and patterns that are exposed here. Interested readers are referred to [Martin99]. Architecture and Dependencies What goes wrong with software? The design of many software applications begins as a vital image in the minds of its designers. At this stage it is clean, elegant, and com- pelling. It has a simple beauty that makes the designers and implementers itch to see it working. Some of these applications manage to maintain this purity of design through the initial development and into the first release. But then something begins to happen. The software starts to rot. At first it isn’t so bad. An ugly wart here, a clumsy hack there, but the beauty of the design still shows through. Yet, over time as the rotting continues, the ugly festering sores and boils accumulate until they dominate the design of the application. The program becomes a festering mass of code that the developers find increasingly hard to maintain. Eventu- 1. [Shaw96] 2. [GOF96]
  • 25. Copyright (c) 2000 by Robert C. Martin. All Rights Reserved.
  • 27. Single responsibility “A class should have one, and only one, reason to change”
  • 28. api domain weather city interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 main suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O
  • 29. Open closed “You should be able to extend a classes behavior, without modifying it”
  • 32. Liskov substitution “Derived classes must be substitutable for their base classes”
  • 33. open class Rectangle( open var width: Int, open var height: Int ) { fun area() = width * height } class Square(size: Int) : Rectangle(size, size) { override var width: Int = size set(value) { field = value if (height != value) height = value } override var height: Int = size set(value) { field = value if (width != value) width = value } }
  • 34. var rectangle = Rectangle(2, 3) var initialArea = rectangle.area() rectangle.width *= 2 println(initialArea * 2 == rectangle.area()) rectangle = Square(2) initialArea = rectangle.area() rectangle.width *= 2 println(initialArea * 2 == rectangle.area())
  • 35. Interface segregation “Make fine grained interfaces that are client specific”
  • 38. Dependency inversion “Depend on abstractions, not on concretions”
  • 39. main suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O api domain weather city interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 40. Dependency inversion “High level modules should not depend upon low level modules. Both should depend upon abstractions”
  • 41. main suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O api domain weather city interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 42. domain city interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3
  • 43. domain city class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 44. domain city class WeatherUseCase interface CityRetriever class JsonCityRetriever : CityRetriever domain city class WeatherUseCase interface CityRetriever class JsonCityRetriever : CityRetriever
  • 45. domain city class WeatherUseCase interface CityRetriever class JsonCityRetriever : CityRetriever domain city class WeatherUseCase interface CityRetriever class JsonCityRetriever : CityRetriever
  • 46. “I declare an interface just to implement dependency inversion or when there are multiple implementations” Anonymous at Droidcon London
  • 47. “Declare an interface only to implement a S.O.L.I.D. principle”
  • 48. main suspend fun main(args: Array<String>) { val api = RetrofitFactory.createService<WeatherApi>() val weatherRepository = OpenWeatherTemperatureRepository(api) val cityRetriever = JsonCityRetriever() val useCase = WeatherUseCase(cityRetriever, weatherRepository) val result = useCase.getCityData(args[0]) println(result) }O api domain weather city interface WeatherApi { @GET("weather?appid=$OPEN_WEATHER_APP_ID&units=metric") fun currentWeather(@Query("id") id: Int): Deferred<TemperatureWrapper> @GET("forecast?appid=$OPEN_WEATHER_APP_ID&units=metric") fun forecast(@Query("id") id: Int): Deferred<Forecast> }Z class OpenWeatherTemperatureRepository( private val api: WeatherApi ) : TemperatureRepository { override suspend fun getTemperature(cityId: Int): Temperature { val forecastDeferred = api.forecast(cityId) val weather = api.currentWeather(cityId).await() val temperatures = forecastDeferred.await().list.map { it.main } return Temperature( weather.main.temp.toInt(), temperatures.map { it.temp_min }.min()?.toInt(), temperatures.map { it.temp_max }.max()?.toInt() )D }E }F interface TemperatureRepository { suspend fun getTemperature(cityId: Int): Temperature }7 class WeatherUseCase( private val cityRetriever: CityRetriever, private val repository: TemperatureRepository) { suspend fun getCityData(s: String): String { return try { val cities = cityRetriever.findCity(s) if (cities.isEmpty()) { "No city found" } else { val city = cities.first() val temperature = repository.getTemperature(city.id) "$city - $temperature" }0 } catch (e: Exception) { "Error retrieving data: ${e.message}" }1 }2 }3 interface CityRetriever { fun findCity(name: String): List<City> }6 class JsonCityRetriever : CityRetriever { private val allCities by lazy { val text = javaClass.getResource("/cityList.json").readText() Gson().fromJson<List<CityJson>>(text) }A override fun findCity(name: String): List<City> { return allCities .filter { it.name == name } .map { City(it.id, it.name, it.country) } .toList() }B }C
  • 63. Architecture Vs code conventions TDD Pros Cons More code Framework independence entitiesentitiesentitiesentities domain repository UI data source presenter
  • 64. Links Demo Project github.com/fabioCollini/CleanWeather 2000 - Robert C. Martin - Design Principles and Design Patterns www.cvc.uab.es/shared/teach/a21291/temes/object_oriented_design/ materials_adicionals/principles_and_patterns.pdf 2005 - Robert C. Martin - The Principles of OOD butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod 2012 - Robert C. Martin - The Clean Architecture 8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html