Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Kotlin Primer nötig? 2 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

1 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Migrate | Recap 5 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Sprache außerhalb der Sprache 11

Slide 12

Slide 12 text

Aber Spring zwingt dich doch nicht dazu. — Eine Entwicklerin 12 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

2 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Migrate | Recap 15 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Exkursion: Lifecycle-Adapter von Spring zu Domain bauen !" package: domain interface Lifecycle { fun start() fun stop() } !" package: main class SpringLifecycleAdapter(private val components: Set): 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

Slide 22

Slide 22 text

Domain sollte frei von Spring sein 22 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 23

Slide 23 text

3 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Migrate | Recap 23 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 24

Slide 24 text

• Unsere Anwendung --> • Was ist das Ziel unseres Anwendungs- und Testdesigns? 24 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 25

Slide 25 text

Ziel Kontinuierliche Sicherheit, dass unsere Domain Logik noch geht 25 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 26

Slide 26 text

26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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() 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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

4 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Migrate | Recap 31 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 32

Slide 32 text

Kotlin Bean DSL 10 val beans = beans { !" Klasse definieren bean() !" Bean-Implementierung !" und sein !$interface PoetrySlamApi!$ definieren bean() !" Bean per Supplier definieren bean { InMemoryPoems() } } 10 Seit 2016 mit Spring 5 http:/ /bit.ly/argh-beandsl 32 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 33

Slide 33 text

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() } } } !" check environment val myBeans = beans { if("onlybackend" in env.activeProfiles){ !" do sth. } } 33 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 34

Slide 34 text

beans {} können andere referenzieren fun fooConfig() = beans { bean() bean{ val jpaPoems = ref() PoetrySlam(jpaPoems) } } 34 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 35

Slide 35 text

Bean DSL statt @ComponentScan @Controller class PoetrySlamController(private val poetrySlamApi: PoetrySlamApi){ !" !# } interface PoetrySlamApi { !" !# } @EnableAutoConfiguration class MyApplication fun main(args: Array) { val beans = beans { bean() !" !# } val context = runApplication(*args){ addInitializers(beans) } } 35 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

5 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Migrate | Recap 37 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

6 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Migrate | Recap 41 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Kofu Beispiel: Minimal Servlet val myApp = application(WebApplicationType.SERVLET) { webMvc { port = 8080 } !" r2dbcH2 { !!# } !" logging { !!# } !" listener { !!# } } fun main(args: Array) { myApp.run(args) } !" optional: place an index.html in src/main/resources to avoid 404 44 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

7 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Hybrid | Recap 46 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 47

Slide 47 text

Functional Beans mit alten Beans mischen @Configuration open class MyOldConfiguration { "# "$ } fun createProdBeans() = beans { "# "$ } @Import(MyOldConfiguration"%class) @EnableAutoConfiguration class MyApplication fun main(args: Array) { runApplication(*args){ addInitializers(createProdBeans()) } } 47 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 48

Slide 48 text

Beans in Tests benutzen class FunctionalConfigAdapter: ApplicationContextInitializer { 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

Slide 49

Slide 49 text

8 Spring Vs Boot | Container | Design4Testability | SpringBeans | FuncConfig | Kofu | Hybrid | Recap 49 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Backup 54 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Kofu Beispiel: Properties val myApp = application(WebApplicationType.SERVLET) { configurationProperties(prefix = "poetry-slam") webMvc { port = 8080 } } data class PoetrySlamProperties(val name: String) fun main(args: Array) { val context = myApp.run(args) val properties = context.getBean() 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

Slide 57

Slide 57 text

Kofu Beispiel: Trennen von Web, Data und Domain val webConfig = configuration { beans { bean(); bean("#poetrySlamRouter) } webMvc { port = 8080 converters { string(); jackson() } } listener { 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) { myApp.run(args) } 57 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 58

Slide 58 text

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) = runApplication(*args){ addInitializers(kapaCoreDependencyConfigs) addInitializers(kapaCoreConfigs) } 58 @arghrich - CC BY 4.0 | speakerdeck.com/richargh

Slide 59

Slide 59 text

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