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

Spring Boot - The Good Parts [DE][Entwicklertag 2020]

Richard
February 20, 2020

Spring Boot - The Good Parts [DE][Entwicklertag 2020]

Entwicklertag 2020: https://entwicklertag.de/frankfurt/2020/spring-boot-%E2%80%93-good-parts-kotlin-level-beginner

Spring ist ein Framework, dass seit 15 Jahren kontinuierlich neue Features bekommt und mit Spring Boot ganz vorne am Zahn der Zeit ist. Die lange Entwicklungszeit führt allerdings auch dazu, dass es für ein Problem weit mehr als eine Lösung im Framework gibt. Viele dieser Features sind den Tradeoff allerdings nicht wert, da sie zwar schnell eingebaut sind aber den Code dann verkomplizieren und die Einstiegshürde für neue Entwickler erhöhen. Dabei kann Spring Boot bereits jetzt explizit, offensichtlich und simpel sein, Beispiele dafür sind nur rar gesät.

In dieser Präsentation schauen wir uns Kotlin Code an der die modernen Features BeanDsl, Functional Web und Spring Fu nutzt. Mit diesen Features können wir erreichen, dass Code wieder innerhalb von Klassen geschrieben wird statt außerhalb in einer eigenwilligen Annotationsprache. Das senkt die Einstiegshürde wiederrum, weil jetzt wieder die Syntax und Kontrollfluss unserer Programmiersprache gilt.

Projekte beginnen oft nicht auf einer grünen Wiese, deswegen betrachten wir auch wie man ein bestehendes Spring Boot Projekt inkrementell auf explizites Verhalten umstellen kann. Am Ende haben wir eine Anwendung deren Kontrollfluss wir in einer Stunde erklären können und nicht mehr raten müssen was zuerst passiert.

Richard

February 20, 2020
Tweet

More Decks by Richard

Other Decks in Programming

Transcript

  1. Spring Boot The Good Parts 1 2 3 4 4

    Mein Spring FuncHybrid Beispiel ist: http:/ /bit.ly/argh-spring-price 3 Mein Spring Functional Beispiel ist: http:/ /bit.ly/argh-spring-func 2 Ein gutes Codebeispiel ist: https:/ /github.com/sdeleuze/spring-messenger 1 Inspiriert von "Sébastien Deleuze @ Spring I/O 2019" (Vortrag inkl. Kofu Checking): http:/ /bit.ly/argh-springio-func Richard Gross - pronoun.is/he - User-Need Developer | @arghrich - CC BY 4.0 | speakerdeck.com/richargh | richargh.de
  2. Kotlin Primer 1 !" define class and constructor & fields

    class Foo constructor(val name: String) !" functions don't need to be in classes fun main(){ !" types are inferred val name = "Trick" !" new is not required for instance val foo1 = Foo("bar") } !" functions can have method bodies fun doSth(): String { return "Track" } !" functions can be expressions fun doSth(): String = "Track" 3 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  3. Kotlin Primer 2 !" function parameters can be other functions

    fun display(transform: (Int) !# Int){ val t = transform(42); !$ display t somehow !% } !" we can pass functions like this fun main(){ display({ num !# num + 1}) } !" if our function is the !&last!& argument, we don't need () fun main(){ display { num !# num + 1} } 4 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  4. 1 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Migrate | Recap 5 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  5. Spring Boot • Kombiniert alle Spring Projekte (Framework, Data, Security,

    ...) • Spring Projekte meistens Abstraktionen (über ext. Libraries) • Garantiert, dass Projekte und ihre ext. Libraries zueinander passen • Konfiguriert alle Dependencies "Automagisch" durch Starter und @ConditionalOn... • Bietet Actuator: Metrics, Health Checks ... • Stellt viel Tooling bereit 8 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  6. Spring Boot • Kombiniert alle Spring Projekte (Framework, Data, Security,

    ...) • Spring Projekte meistens Abstraktionen (über ext. Libraries) • Garantiert, dass Projekte und ihre ext. Libraries zueinander passen • Konfiguriert alle Dependencies "Automagisch" durch Starter und @ConditionalOn... • Bietet Actuator: Metrics, Health Checks ... 9 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  7. Spring+Boot: Bad Parts • Nichts geht ohne den Spring Context.

    • Spring Context braucht implizites und langsames @ComponentScan + @EnableAutoConfiguration • Principle of Most Astonishment -> "Wo kommt die H2 her?" • Un-discoverable Programmiersprachen außerhalb von Java/Kotlin • interface PersonRepository { findByEmailAndLastname(mail, name) } • @ConditionalOn... 10 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  8. Aber Spring zwingt dich doch nicht dazu. — Eine Entwicklerin

    12 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  9. Wie denn? Alle Beispiele "zwingen" mich doch? — Ich vor

    2 Jahren 13 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  10. Spring Boot - Wie man von Bad zu Good kommt

    Bad Good Langsamer Context Kern für Tests Spring-frei halten @ComponentScan Explizite Kotlin beans{} Programmiersprachen außerhalb von Java/Kotlin FuncConfig @EnableAutoConfiguration Kofu & Least-Astonishment 14 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  11. 2 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Migrate | Recap 15 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  12. Spring Docs on Testing The POJOs that make up your

    app should be testable in JUnit or TestNG tests, with objects instantiated by using the new operator, without Spring or any other container. Emphasizing [these] tests as part of your development methodology can boost your productivity. — Spring Framework Docs - Testing http:/ /bit.ly/argh-spocs-unit 16 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  13. If you can exist without a registry/ container/framework do so

    5 I (among others) was wrong to shove DI containers down people’s throats in 2002 [...]. [We did this because] J2EE apps didn’t have primordial entry points where you could compose everything. — Paul Hammant 6 7 7 It turns out we didn't need containers after all http:/ /bit.ly/richargh-hammant-di 6 Autor von http:/ /picocontainer.com/team.html 5 DI versus BUFA (Paul Hammant) http:/ /bit.ly/richargh-divbufa 17 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  14. Container sind hilfreich wenn wir 1. Beans ganz rechts austauschen

    müssen 2. Deklarative Bean-Composition wollen 3. Lifecycle Management brauchen 19 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  15. Container machen aber auch Tests langsam 1. Daher Spring an

    den Rand drängen 2. Bei Bedarf erzeugt und injected Container kleine Factories in Domain 8 3. Container delegieren Lifecycle- Änderungen an Domain 4. --> Domain-Tests benötigen keine Container und sind schnell 8 Dependency Injection Inversion (Robert Martin) http:/ /bit.ly/richargh-di-inv 20 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  16. Exkursion: Lifecycle-Adapter von Spring zu Domain bauen !" package: domain

    interface Lifecycle { fun start() fun stop() } !" package: main class SpringLifecycleAdapter(private val components: Set<Lifecycle>): ApplicationRunner, DisposableBean { override fun run(args: ApplicationArguments?) = components.forEach { component !$ component.start() } override fun destroy() = components.forEach { component !$ component.stop() } } 21 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  17. Domain sollte frei von Spring sein 22 @arghrich - CC

    BY 4.0 | speakerdeck.com/richargh
  18. 3 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Migrate | Recap 23 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  19. • Unsere Anwendung --> • Was ist das Ziel unseres

    Anwendungs- und Testdesigns? 24 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  20. Ziel Kontinuierliche Sicherheit, dass unsere Domain Logik noch geht 25

    @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  21. 26

  22. Manche Small-Tests brauchen Poems interface Poems { operator fun get(poemId:

    PoemId): Poem? "# add, find, etc. } 27 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  23. Prod & Test benutzen daher unterschiedliche Repos interface Poems {

    fun size(): Int, operator fun get(poemId: PoemId): Poem? "# add, find, etc. } "# test class InMemoryPoems: Poems { private val entities = ConcurrentHashMap<PoemId, Poem>() override fun get(id: PoemId) = entities.get(id) override fun clear() = entities.clear() } "# prod class SpringJpaPoems("% "&): Poems { "% "& } 28 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  24. Repos synchron halten mit Contracts9 !" does the interface satisfy

    the contract? !# abstract class PoemsContract(private val poems: Poems){ @Test fun `is empty when no poem has been added`() { !$ given: no poems have been added !$ when: I ask for the size val size = poems.size() !$ then: it is empty assertThat(size).isEqualTo(0) } !$ !!% } !$ checking fake @Tags(Tag(contract), Tag(small)) class InMemoryFakePoemsTest: PoemsContract(InMemoryFakePoems()) !$ checking prod @Tags(Tag(contract), Tag(medium)) @AllRequiredSpringTestAnnotations class SpringJpaPoemsTest(val springJpaPoems: SpringJpaPoems): PoemsContract(springJpaPoems) 9 Contract Tests by J. B. Rainsberger http:/ /bit.ly/richargh-contractchecks 29 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  25. Ziel erreicht • Domain bleibt frei von Spring • -->

    Mit Spring Beans definieren wir nun die Interface- Implementierung (=Bean) 30 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  26. 4 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Migrate | Recap 31 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  27. Kotlin Bean DSL 10 val beans = beans { !"

    Klasse definieren bean<PoetrySlamController>() !" Bean-Implementierung !" und sein !$interface PoetrySlamApi!$ definieren bean<PoetrySlam>() !" Bean per Supplier definieren bean<Poems> { InMemoryPoems() } } 10 Seit 2016 mit Spring 5 http:/ /bit.ly/argh-beandsl 32 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  28. beans {} ist normaler Kotlin Code !" assign as value

    val webbeans = beans { !#!!$!% } !" if/else/loop is allowed fun databeans(usePostgres: Boolean): BeanDefinitionDsl { return beans { if(usePostgres){ bean<SpringJpaPoems>() } } } !" check environment val myBeans = beans { if("onlybackend" in env.activeProfiles){ !" do sth. } } 33 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  29. beans {} können andere referenzieren fun fooConfig() = beans {

    bean<SpringJpaPoems>() bean<PoetrySlamApi>{ val jpaPoems = ref<SpringJpaPoems>() PoetrySlam(jpaPoems) } } 34 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  30. Bean DSL statt @ComponentScan @Controller class PoetrySlamController(private val poetrySlamApi: PoetrySlamApi){

    !" !# } interface PoetrySlamApi { !" !# } @EnableAutoConfiguration class MyApplication fun main(args: Array<String>) { val beans = beans { bean<PoetrySlamController>() !" !# } val context = runApplication<MyApplication>(*args){ addInitializers(beans) } } 35 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  31. Ziel erreicht • Kotlin Beans DSL ist normaler, expliziter, schneller

    Kotlin-Code • --> Wir können auch andere Spring Projekts funktional&explizit konfigurieren 36 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  32. 5 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Migrate | Recap 37 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  33. SpringFramework.web RouterFunctionDsl class PoetrySlamHandler { fun index(serverRequest: ServerRequest) = ServerResponse.ok().body("Hi")

    fun authors(serverRequest: ServerRequest) = ServerResponse.ok() .body(listOf("Macy", "Diddy", "Kevin")) } fun poetrySlamRouter(poetrySlamHandler: PoetrySlamHandler) = router { accept(APPLICATION_JSON).nest { GET("/", poetrySlamHandler!"index) "/authors".nest { GET("/", poetrySlamHandler!"authors) } } } 38 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  34. SpringFramework.data.r2dbc DatabaseClient fun main() { val client = createClient() !"

    functional db query val affectedRows = client .execute("SELECT * from Poems") .fetch().rowsUpdated() } private fun createClient(): DatabaseClient{ val h2config = H2ConnectionConfiguration.builder().url("mem:testdb").build() val connectionFactory = H2ConnectionFactory(h2config) return DatabaseClient.create(connectionFactory) } 39 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  35. Ziel erreicht • Spring stellt explizite und schnelle Funktionen zum

    Konfigurieren von Web & Data bereit • --> In Zukunft können wir auch die Spring Projekts explizit konfigurieren 40 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  36. 6 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Migrate | Recap 41 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  37. Kofu: Kotlin Functional 11 Kofu is a minimalist, efficient and

    explicit configuration model for Spring Boot, using a Kotlin DSL [...] with great discoverability thanks to auto-complete [...] with fast startup. [...] It is not intended to be used in production yet. 12 12 Spring Incubator: https:/ /github.com/spring-projects-experimental/spring-fu/tree/master/kofu 11 Basiert auf Spring Functional https:/ /github.com/sdeleuze/spring-kotlin-functional 42 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  38. Baut auf bestehendem Spring auf • beans{} DSL vom Spring

    Framework • router{} DSL vom Spring MVC bzw. Spring WebFlux • Die neue application{} und configuration{} DSL ist ein Adapter für bestehende *AutoConfiguration Klassen 43 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  39. Kofu Beispiel: Minimal Servlet val myApp = application(WebApplicationType.SERVLET) { webMvc

    { port = 8080 } !" r2dbcH2 { !!# } !" logging { !!# } !" listener<ApplicationReadyEvent> { !!# } } fun main(args: Array<String>) { myApp.run(args) } !" optional: place an index.html in src/main/resources to avoid 404 44 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  40. Kofu DSL ist noch nicht fertig • v0.1 (05.2019), v0.2

    (10.2019), v0.3 (01.2020) • DSL ist Adapter für bestehende *AutoConfiguration • Mit DSL sind noch nicht alle Projekte konfigurierbar • Mit DSL ist noch nicht alles vom Projekt konfigurierbar • DSL könnte sich noch ändern bis v1.0 (x.2020?) 45 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  41. 7 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Hybrid | Recap 46 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  42. Functional Beans mit alten Beans mischen @Configuration open class MyOldConfiguration

    { "# "$ } fun createProdBeans() = beans { "# "$ } @Import(MyOldConfiguration"%class) @EnableAutoConfiguration class MyApplication fun main(args: Array<String>) { runApplication<MyApplication>(*args){ addInitializers(createProdBeans()) } } 47 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  43. Beans in Tests benutzen class FunctionalConfigAdapter: ApplicationContextInitializer<GenericApplicationContext> { override fun

    initialize(context: GenericApplicationContext) { val beans = createProdBeans() beans.initialize(context) } } @ExtendWith(SpringExtension"#class) @ContextConfiguration( initializers = [AFunctionalComponentConfig"#class], classes = [AnOldComponentConfig"#class]) class ATest { @Test fun `test something`(@Autowired poetrySlam: PoetrySlam){ "$ ""% } } 48 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  44. 8 Spring Vs Boot | Container | Design4Testability | SpringBeans

    | FuncConfig | Kofu | Hybrid | Recap 49 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  45. Good to Bad Bad Good Langsamer Context Kern für Tests

    Spring-frei halten @ComponentScan Explizite Kotlin beans{} Programmiersprachen außerhalb von Java/Kotlin FuncConfig @EnableAutoConfiguration Bald Kofu & Least-Astonishment 50 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  46. The Good Parts Eine Spring Boot Anwendung geht auch: •

    in Millisekunden testbar mit Springless Domain • schnell • explizit • schrittweise zu migrieren 51 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  47. The Other Good Parts • Oliver Drothbom: Hypermedia=REST, Connascence •

    Sebastien Deleuze: Functional Spring • Josh Long: Spring Blogging und Beispiele • uvm. 52 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  48. Danke • Feedback ---> • Mehr zu Kofu und explicit

    Spring 1 2 • Noch mehr explicit Spring Beispiele 3 4 • Mehr Beans 13 14 • Mehr funktionale Routen 15 15 https:/ /spring.io/blog/2016/09/22/new-in-spring-5-functional-web-framework 14 Mehr Beans https:/ /github.com/sdeleuze/spring-kotlin-functional/blob/master/src/main/ kotlin/functional/Beans.kt 13 Spring 5 Functional: https:/ /spring.io/blog/2017/08/01/spring-framework-5-kotlin-apis-the- functional-way 4 Mein Spring FuncHybrid Beispiel ist: http:/ /bit.ly/argh-spring-price 3 Mein Spring Functional Beispiel ist: http:/ /bit.ly/argh-spring-func 2 Ein gutes Codebeispiel ist: https:/ /github.com/sdeleuze/spring-messenger 1 Inspiriert von "Sébastien Deleuze @ Spring I/O 2019" (Vortrag inkl. Kofu Checking): http:/ /bit.ly/ argh-springio-func 53 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  49. Extension für Reset/Clear schreiben !" extension to reset everything beforeEach

    class ClearResetJunitExtension: org.junit.jupiter.api.extension.BeforeEachCallback { override fun beforeEach(extCon: ExtensionContext) { SpringExtension.getApplicationContext(extCon).getBeansOfType(Clearable!#class.java).forEach { it.value.clear() } SpringExtension.getApplicationContext(extCon).getBeansOfType(Resettable!#class.java).forEach { it.value.reset() } } } !" example usage @Tags(Tag(medium)) @ExtendWith(ClearResetJunitExtension!#class, SpringExtension!#class) @ContextConfiguration(initializers = [AFunctionalComponentConfig!#class]) @TestPropertySource(locations = ["classpath:application.properties"]) !" Optional class ATest { @Test fun `test something`(@Autowired aFactory: AFactory){ val bar = aFactory.bar() !" etc. } } 55 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  50. Kofu Beispiel: Properties val myApp = application(WebApplicationType.SERVLET) { configurationProperties<PoetrySlamProperties>(prefix =

    "poetry-slam") webMvc { port = 8080 } } data class PoetrySlamProperties(val name: String) fun main(args: Array<String>) { val context = myApp.run(args) val properties = context.getBean<PoetrySlamProperties>() println("The slam is called [${properties.name}]") } #$ src/main/resources/application.properties contains poetry-slam.name=The Hits 1789 56 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  51. Kofu Beispiel: Trennen von Web, Data und Domain val webConfig

    = configuration { beans { bean<PoetrySlamHandler>(); bean("#poetrySlamRouter) } webMvc { port = 8080 converters { string(); jackson() } } listener<ApplicationReadyEvent> { println("Application Ready") } } val domainConfig = configuration { "$ ""% "& } val dataConfig = configuration { "$ ""% "& } val myApp = application(WebApplicationType.SERVLET) { enable(domainConfig) enable(webConfig) if(theAnswer "' 42) "( programmatically disable enable(dataConfig) } fun main(args: Array<String>) { myApp.run(args) } 57 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  52. Kofu Application Start !" beans erben von ApplicationContextInitializer internal val

    kapaCoreConfigs = beans { addIntegratingComponentsConfigs() } !" dependencies auf andere Komponenten bewusst rausstellen internal val kapaCoreDependencyConfigs = beans { addSharedKernelConfigs() if(env.isMonolith()){ addLokationseingangConfigs() } } @SpringBootConfiguration @EnableAutoConfiguration open class Application fun main(args: Array<String>) = runApplication<Application>(*args){ addInitializers(kapaCoreDependencyConfigs) addInitializers(kapaCoreConfigs) } 58 @arghrich - CC BY 4.0 | speakerdeck.com/richargh
  53. Spring kann mehr als Annotationen • 2004 Spring 1.0 mit

    Xml Beans • 2009 Spring 3.0 mit @Configuration und @Bean reference/ • 2013 Spring 4.0 mit Groovy beans {} • 2016 Spring 5.0 mit Kotlin beans {} • Die Bean DSL ist nicht experimentell 59 @arghrich - CC BY 4.0 | speakerdeck.com/richargh