kotlin 本地服务_Kotlin微服务

kotlin 本地服务

Here I am, fifty-something and facing, yet again, a new language. Sometimes I wonder why I bother — I could easily Java my way to retirement. But I enjoy the challenge that a new language presents — I like to understand what it’s doing to try to make life easier. Or perhaps it’s my eternal optimism that someone will finally come up with a general-purpose language so simple that no other language will ever have to be created.

我现在五十岁了,正面对着一种新的语言。 有时我想知道为什么要打扰我-我可以轻松地Java退休。 但是我喜欢一种新语言所带来的挑战-我喜欢理解如何使生活变得更轻松。 也许是我永远的乐观,最终有人会想出一种通用语言,它是如此简单,以至于不再需要创建任何其他语言。

Kotlin is a JVM based language and a lot like Java. In fact, IntelliJ, which is heavily promoting Kotlin, has a nice “Convert from Java to Kotlin” menu option for your old Java classes and it does a pretty good job. Kotlin does not have all the excesses of Scala and it works well in the Java ecosystem. So I can still use Maven (or Gradle) for my builds and all of the build plugins that I’m used to. Another nice thing is that it’s compatible with both Spring Boot and WebFlux which makes migration of my WebFlux projects a snap!

Kotlin是一种基于JVM的语言,与Java非常相似。 实际上,大力推广Kotlin的IntelliJ为您的旧Java类提供了一个不错的“从Java转换为Kotlin”菜单选项,并且做得很好。 Kotlin没有Scala的所有多余功能,并且在Java生态系统中运行良好。 因此,我仍然可以将Maven(或Gradle)用于我的构建以及我习惯的所有构建插件。 另一件好事是它与Spring Boot和WebFlux兼容,这使我的WebFlux项目的迁移变得轻而易举!

What’s different about Kotlin?

Kotlin有什么不同?

Well, the dreaded null has been contained. It’s not gone completely — it still has to interact with Java code — but it’s dealt with much more explicitly.

好吧,这个可怕的空值已经被包含了。 它并没有完全消失-它仍然必须与Java代码进行交互-但它的处理要明确得多。

Functions are also first-class objects in Kotlin. That’s not as big a deal for me — single-abstract-method (SAM) interfaces always felt enough like function objects to make me happy.

函数也是Kotlin中的一流对象。 对我而言,这没什么大不了的-单抽象方法(SAM)界面总是感觉像函数对象一样足以让我高兴。

We also got rid of the wild-card types for generics, mostly by being able to specify if a type used in a generic is an innie or an outie. Again, not a big deal for me as I’ve grown used to the wild west of wild-card types.

我们也摆脱了通配符的通配符类型,主要是通过能够指定通配符中使用的类型是innie还是outie。 同样,对于我来说,这不是什么大不了的事情,因为我已经习惯了通配符类型的狂野西部。

What I’m going to do is take one of my existing Java microservice and convert it to Kotlin. I’m going to use my readnews project from my article How to use Kubernetes Cron Jobs to Periodically Read the News as a starting point and create a new project readnewskotlin from it. I won’t copy over any files directly, I’ll just use it for reference. I used the IntelliJ IDEA to create a new Maven project and used the kotlin-archetype-jvm to generate the needed files. You can set up a configuration to run it if you wish, just to make sure everything works.

我要做的是将我现有的Java微服务之一转换为Kotlin。 我会用我的readnews项目从我的文章如何使用Kubernetes Cron工作定期看新闻为出发点,并创建一个新的项目readnewskotlin从它。 我不会直接复制任何文件,我只会将其用作参考。 我使用IntelliJ IDEA创建了一个新的Maven项目,并使用kotlin-archetype-jvm生成了所需的文件。 您可以根据需要设置一个配置以运行该配置,以确保一切正常。

So how do we go about changing from a Java micro-service to a Kotlin one? The first step, if you’re using Maven, is to introduce the Kotlin compiler and disable the Java compiler. That’s done by including two plugins, the kotlin-maven-plugin, and the maven-compiler-plugin. My project from the Maven archetype added the kotlin-maven-plugin for me, but I added the maven-compiler-plugin and a few others that I normally use. So my plugin section of the pom.xml now looks like this:

那么,如何从Java微服务转变为Kotlin呢? 如果您使用的是Maven,则第一步是引入Kotlin编译器并禁用Java编译器。 这可以通过包含两个插件kotlin-maven-pluginmaven-compiler-plugin 。 我来自Maven原型的项目为我添加了kotlin-maven-plugin ,但是我添加了maven-compiler-plugin和其他一些我通常使用maven-compiler-plugin 。 所以我的pom.xml插件部分现在看起来像这样:

<plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.2.0</version>
                <configuration>
                    <to>
                        <image>docker.io/rlkamradt/readnewskotlin</image>
                    </to>
                </configuration>
            </plugin>
            <plugin>
                <groupId>pl.project13.maven</groupId>
                <artifactId>git-commit-id-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>${kotlin.version}</version>
                <executions>
                    <execution>
                        <id>compile</id>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <sourceDirs>
                                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
                                <sourceDir>${project.basedir}/src/main/java</sourceDir>
                            </sourceDirs>
                            <jvmTarget>${java.version}</jvmTarget>
                            <args>
                                <arg>-Xjvm-default=enable</arg>
                            </args>
                        </configuration>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                        <configuration>
                            <sourceDirs>
                                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
                                <sourceDir>${project.basedir}/src/test/java</sourceDir>
                            </sourceDirs>
                            <jvmTarget>11</jvmTarget>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
                <executions>
                    <!-- Replacing default-compile as it is treated specially by maven -->
                    <execution>
                        <id>default-compile</id>
                        <phase>none</phase>
                    </execution>
                    <!-- Replacing default-testCompile as it is treated specially by maven -->
                    <execution>
                        <id>default-testCompile</id>
                        <phase>none</phase>
                    </execution>
                    <execution>
                        <id>java-compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>java-test-compile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>

I needed to add the Spring Boot parent and dependencies to the Kotlin standard library dependencies, so I replaced the dependencies section with this:

我需要将Spring Boot父项和依赖项添加到Kotlin标准库依赖项中,因此我将依赖项部分替换为:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <kotlin.version>1.3.72</kotlin.version>
        <kotlin.code.style>official</kotlin.code.style>
        <junit.version>4.12</junit.version>
        <kotlinx.version>1.0.0</kotlinx.version>
        <assertj.version>3.12.0</assertj.version>
        <junit.platform.version>1.3.2</junit.platform.version>
        <boot.dependencies.version>2.2.6.RELEASE</boot.dependencies.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${boot.dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>6.3</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jdk8</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>


        <dependency>
            <groupId>org.jetbrains.kotlinx</groupId>
            <artifactId>kotlinx-coroutines-core</artifactId>
            <version>${kotlinx.version}</version>
        </dependency>


        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-kotlin</artifactId>
        </dependency>


        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-test-junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


    </dependencies>

That gives me everything I need to start creating microservices in Kotlin.

这给了我开始在Kotlin中创建微服务所需的一切。

The next thing I did was delete the code that the archetype generated for me, though I kept the directory structure. To this, I added the application class that Spring wants, to know where to start. I created a file ReadNewsApplication.kt with the following definitions:

我要做的第二件事是删除原型为我生成的代码,尽管我保留了目录结构。 为此,我添加了Spring想要的应用程序类,以知道从哪里开始。 我使用以下定义创建了文件ReadNewsApplication.kt

@SpringBootApplication
class ReadNewsApplicationfun main(args: Array<String>) {
SpringApplication.run(ReadNewsApplication::class.java, *args)
}

Notice that the main function is outside of the class (which is empty) as Kotlin has no static functions.

注意,由于Kotlin没有静态函数,所以main函数在类之外(为空)。

Let’s take a look at the data class we are using in this service. I called the file Inserts.kt:

让我们看一下此服务中使用的数据类。 我将文件称为Inserts.kt

class Inserts(
@Id val id: String,
val status: String? = null,
val totalResults: Int? = null,
val articles: List<Articles>? = null)class Articles(
val source: Source? = null,
val author: String? = null,
val title: String? = null,
val description: String? = null,
val url: String? = null,
val urlToImage: String? = null,
val publishedAt: String? = null,
val content: String? = null)class Source (
private val id: String? = null,
private val name: String? = null)

Kotlin has a special class type called data class which we might have used, however, the accessor functions it generates don’t play well with the MongoDB driver we’re using. So we use simple classes with the fields defined in the primary constructor. Notice that the primary constructor is just appended to the class name and all the initial values are set there. Also, note the ? after the type — this indicates that this is a value that can have null.

Kotlin有一个特殊的类类型,称为data class ,我们可能已经使用过,但是,它生成的访问器函数在我们使用的MongoDB驱动程序中不能很好地发挥作用。 因此,我们将简单类与主要构造函数中定义的字段一起使用。 请注意,主构造函数仅附加在类名后面,并且所有初始值都在此设置。 另外,请注意? 类型之后—这表示这是一个可以为null的值。

But didn’t Kotlin get rid of null? No — it still has to deal with the output from any Java libraries. The way we specify that is to append the question mark to the type, so the compiler knows to enforce null checks as necessary. And how do we do a null check in Kotlin if there is no null? The Elvis operator ?: (so-called because the sideways emoji looks like Elvis with his quiffed hair!) will help with that. Take this function:

但是Kotlin不是没有摆脱空吗? 不,它仍然必须处理任何Java库的输出。 我们指定的方式是将问号附加到类型上,因此编译器知道在必要时强制执行null检查。 如果没有空值,我们如何在Kotlin中进行空值检查? 猫王运算符?:之所以这么称呼,是因为侧面表情符号看起来像是他的头发蓬松的猫王!)将对此有所帮助。 使用此功能:

fun deNull(x: String?):String = x ?: "elvis"

In this case, x can be null (because its type is String?), so the function uses the ?: to return x if it not null and Elvis if it is. The return type is String without the ? so you never have to worry about null checking it — the compiler can relax and have a drink.

在这种情况下,x可以为null(因为其类型为String? ),因此该函数使用?:返回x(如果它不为null)和Elvis(如果它为null)。 返回类型是不带? String ? 因此,您不必担心对null进行检查-编译器可以放松并喝一杯。

The database repository interface is unchanged except that the implements keyword is replaced with ::

数据库存储库接口除了不变的implements关键字替换为:

@Repository
interface InsertsReactiveRepository :
ReactiveMongoRepository<Inserts, String>

Next, we can get to the controller class. First, here’s the Java version:

接下来,我们可以进入控制器类。 首先,这是Java版本:

@RestController
@RequestMapping("/v1/headlines")
public class ReadHeadlinesControllerV1 {
private static final int MAX_LIMIT = 1000;

private final InsertsReactiveRepository newsReactiveRepository;

ReadHeadlinesControllerV1(
final InsertsReactiveRepository newsReactiveRepository) {
this.newsReactiveRepository = newsReactiveRepository;
}

@GetMapping(path="",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux<Inserts.Articles> getFromMongo(final Instant from,
final Instant to,
final Long limit) {
long actualLimit = limit == null
|| limit == 0
|| limit > MAX_LIMIT
? MAX_LIMIT
: limit;
return newsReactiveRepository
.findAll()
.flatMap(r -> Flux.fromIterable(r.getArticles()))
.filter(r -> filterByDate(r, from, to))
.limitRequest(actualLimit);
}
private boolean filterByDate(
final Inserts.Articles record,
final Instant from,
Instant to) {
if(record == null || record.getPublishedAt() == null) {
return false;
}
Instant theDate;
try {
theDate = Instant.parse(record.getPublishedAt());
} catch(Exception ex) {
return false;
}
return (from == null || theDate.isAfter(from)) &&
(to == null || theDate.isBefore(to));
}
}

And here’s the Kotlin version:

这是Kotlin版本:

@RestController
@RequestMapping("/v1/headlines")
class ReadHeadlinesControllerV1(
val newsReactiveRepository: InsertsReactiveRepository) {
val maxLimit = 1000L
@GetMapping(path = [""],
produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun getFromMongo(
@RequestParam(value="from", defaultValue="") from: String,
@RequestParam(value="to", defaultValue="") to: String,
@RequestParam(value="limit", defaultValue="1000") limit: Long): Flux<Articles> =
newsReactiveRepository
.findAll()
.flatMap { r -> Flux.fromIterable(r.articles) }
.filter { r -> filterByDate(Instant.parse(r.publishedAt), from, to) }
.limitRequest(if (limit > maxLimit) maxLimit else limit)
fun filterByDate(published: Instant, from: String, to: String) =
from.isBlank() || published.isAfter(Instant.parse(from))
&& to.isBlank() || published.isBefore(Instant.parse(to))
}

A bit more concise without loss of information. I should point out a couple of things.

更加简洁,不会丢失任何信息。 我应该指出两点。

The primary constructor immediately following the class name is a nice space-saving device that’s quite intuitive. For the flatMap and filter function, if the last parameter is a function, it can be placed on the outside of the parentheses (in this case the function is the only parameter, so the parentheses are omitted).

紧随类名之后的主要构造函数是一个非常节省空间的设备,非常直观。 对于flatMapfilter函数,如果最后一个参数是一个函数,则可以将其放在括号的外部(在这种情况下,该函数是唯一的参数,因此省略了括号)。

To test it out, I copied over the docker-compose.yaml from the readnews project which will start up MongoDB and the news-reading app created in my previous article. You need to have a key from newsapi.org and set it to the NEWSAPI_KEY environment variable. Then you can start up MongoDB and run the news-reading app:

为了进行测试,我从readnews项目复制了docker-compose.yaml ,该项目将启动MongoDB和我在上一篇文章中创建的新闻阅读应用程序。 您需要具有来自newsapi.org的密钥,并将其设置为NEWSAPI_KEY环境变量。 然后,您可以启动MongoDB并运行新闻阅读应用程序:

docker-compose up -d mongodb
docker-compose up app

I have to start them separately so that MongoDB has a chance to start before the app tries to insert into it. The app should run, spit out some logs and then quit. Now you can start the new app. In the IntelliJ IDEA you can create a new configuration, point it at the main class, and then run it. Once it starts up, you can curl:

我必须分别启动它们,以便MongoDB在应用程序尝试插入之前有机会启动。 该应用程序应运行,吐出一些日志,然后退出。 现在,您可以启动新应用了。 在IntelliJ IDEA中,您可以创建一个新配置,将其指向主类,然后运行它。 一旦启动,您可以卷曲:

curl http://localhost:8080/v1/headlines

It should dump out all the news you didn’t want to read. Don’t forget to docker-compose down afterward to stop MongoDB from being an unwelcome guest in your laptop.

它应该转储所有您不想阅读的新闻。 不要忘docker-compose down稍后进行docker-compose down ,以阻止MongoDB成为您笔记本电脑中不受欢迎的客人。

So that is all there is to creating a simple Kotlin microservice. Obviously, that’s not all there is to Kotlin, but I’ll continue to explore that and find ways to use it to simplify my life as a developer. Honestly, I’m glad I worked through this exercise — Kotlin seems to be a real improvement over Java, without the unreadability of Scala.

这就是创建简单的Kotlin微服务的全部。 显然,这并不是Kotlin的全部功能,但我将继续探索并找到使用它的方法来简化我作为开发人员的生活。 老实说,我很高兴我完成了本练习— Kotlin似乎是对Java的真正改进,而且没有Scala的可读性。

Thanks for sticking with this old man as I work my way through yet another iteration of the future!

感谢您在我继续前进的过程中坚持与这位老人!

Repositories used in this article:

本文中使用的存储库:

Articles referenced in this article:

本文引用的文章:

翻译自: https://medium.com/better-programming/kotlin-microservices-34e7f4fee4ae

kotlin 本地服务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值