(FrĂĽhlings-) Zustand in der (FrĂĽhlings-) Schale: keine einzelne Produktion

Hey, was ist mit einer interaktiven Shell mit automatischer Vervollständigung, Hilfe und vielem mehr? Und ohne Probleme und sogar auf der JVM?


Der Postbote bereitete mir Kopfschmerzen bei der Arbeit. Obwohl wir alle in Worten TDDs sind und durch das rote Licht von Cucumber sehen, was genau kaputt gegangen ist, musste ich in der Praxis viel häufiger REST-Anfragen in der Postman-Oberfläche senden. Zu Beginn der Arbeit war es erforderlich, ein Authentifizierungstoken zu erhalten (eine Anforderung zur Erstellung und eine Anforderung zur Validierung, die Benutzer sind unterschiedlich) und dann von den Lesezeichen herunterzuladen, die Parameter zu bearbeiten und andere Anforderungen zu starten. Klick-Klick-Klick. In einer anderen Reihenfolge. Ich habe bereits Skripte mit cURL geschrieben und Anfragen in IDEA erkannt - das ist nicht bequem. Als idealer Motivator für die Umstellung auf automatische Tests waren dies nur Anfragen, um zu verstehen, was zu einem bestimmten Zeitpunkt in der Kombination aus dem Status des Dienstes, seiner Version, den dahinter liegenden Mainframes und dem Wetter im Haus geschah, und fielen sicherlich nicht unter Regressionstests. Klick-Klick-Klick begann zu viel Zeit zu verbrauchen und erhöhte die Laufleistung der Maus.


Spring Shell, shell , Spring. , . , — . — shell, . , — Spring State Machine. , — , — , , Kotlin, Akka "" . State Machine , .



Spring Initializr Gradle Kotlin. .


curl 'https://start.spring.io/starter.zip?type=gradle-project&language=kotlin&bootVersion=2.2.6.RELEASE&baseDir=shell-state&groupId=me.votez.spring&artifactId=shell-state&name=shell-state&description=Demo%20project%20for%20Spring%20State%20Machine%20and%20Shell&packageName=me.votez.spring.shellstate&packaging=jar&javaVersion=1.8' --compressed --output shellstate.zip && unzip shellstate.zip

Spring Shell gradle


    implementation("org.springframework.shell:spring-shell-starter:2.0.0.RELEASE")


(shell). , ShellComponent ( ) , ShellMethod , . , ShellOption. , Bean Validation. , Enum ( , help).


— . — , , actuator .…
( runBlocking — )


package me.votez.spring.shellstate

import org.springframework.shell.standard.ShellComponent
import org.springframework.shell.standard.ShellMethod
import org.springframework.shell.standard.ShellOption
import kotlinx.coroutines.*
import org.springframework.shell.standard.ShellMethodAvailability
import java.util.*
import kotlin.random.Random

@ShellComponent
class ServerCommands {
    private var token: String? = null

    @ShellMethod("Authenticate and obtain token")
    fun login(
           @ShellOption name:String, 
           @ShellOption password:String, 
           @ShellOption(defaultValue = "admin") scope: String) = runBlocking {
            token = UUID.randomUUID().toString()
            delay(2_000)
            "Connected"
        }
    }

    @ShellMethod("List projects regstered on server")
    fun list(
        @ShellOption(defaultValue = "PROJECT", help = "Possible values are PROJECT and USER") type:EntityType) = when(type) {
        EntityType.PROJECT -> listOf("Roga i Kopita", "Svetliy Put", "NIICHAVO")
        EntityType.USER -> listOf( "Ivanov", "Petrov", "Sidorov")
    }
}

enum class EntityType {
    PROJECT,
    USER
}

. IntelliJ IDEA, Spring Shell JLine, Linux, MacOS Windows, IDE — .


$ bash ./gradlew build
$ java -jar build/libs/shell-state-0.0.1-SNAPSHOT.jar 

help, . clear, exit, quit, script, stacktrace.


2020-05-03 12:49:35.442  INFO 23211 --- [           main] m.v.s.s.ShellStateApplicationKt          : Started ShellStateApplicationKt in 1.036 seconds (JVM running for 0.936)
shell:>help
AVAILABLE COMMANDS

Built-In Commands
        clear: Clear the shell screen.
        exit, quit: Exit the shell.
        help: Display help about available commands.
        script: Read and execute commands from a file.
        stacktrace: Display the full stacktrace of the last error.

Server Commands
        list: List projects registered on server
        login: Authenticate and obtain token

shell:>help list

NAME
        list - List projects registered on server

SYNOPSYS
        list [[--type] entity-type]  

OPTIONS
        --type  entity-type
                Possible values are PROJECT and USER
                [Optional, default = PROJECT]

. login list L, . l TAB , o TAB, login. . , > login root 123456 , — Connected. list .



list , list . :



    @ShellMethodAvailability("list")
    fun listAvailable() = if (token != null) Availability.available()  else Availability.unavailable("cannot run without auth token")

shell, , . help , .


2020-05-02 13:30:12.593  INFO 4443 --- [           main] m.v.s.s.ShellStateApplicationKt          : Started ShellStateApplicationKt in 1.525 seconds (JVM running for 1.252)
shell:>list
Command 'list' exists but is not currently available because cannot run without auth token
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
shell:>help
AVAILABLE COMMANDS

Built-In Commands
        clear: Clear the shell screen.
        exit, quit: Exit the shell.
        help: Display help about available commands.
        script: Read and execute commands from a file.
        stacktrace: Display the full stacktrace of the last error.

Server Commands
      * list: List projects regstered on server
        login: Authenticate and obtain token

Commands marked with (*) are currently unavailable.
Type `help <command>` to learn more.

shell:>help list

NAME
        list - List projects registered on server

SYNOPSYS
        list [[--type] entity-type]  

OPTIONS
        --type  entity-type
                Possible values are PROJECT and USER
                [Optional, default = PROJECT]

CURRENTLY UNAVAILABLE
        This command is currently not available because cannot run without auth token.


, . , , , . command prompt, . , application.properties — , Spring Framework ( ). prompt , .


PrompProvider, , JLine.


@ConfigurationProperties(prefix="shell")
class ServerCommands : PromptProvider

    lateinit var env: String //   application.properties

    override fun getPrompt(): AttributedString =
            AttributedString("${env}:>",
                    AttributedStyle.DEFAULT.foreground(
                            if (token == null) AttributedStyle.YELLOW else AttributedStyle.GREEN))

, . .


, , (soap, protobuf ) — , - . , .


Spring State Machine


, . . , , , . , … , .


Spring State Machine.


, (), -. — The Evolution of Trust. , () . xml — , - .


, . , "" . Spring State Machine , , .


, . , . — Spring SM , . " ", - , - , , — .


, , . - — , .


?


, .

, "" . . ( ) , Thread Pool, shell, SSM - — .


command prompt " ", .


, . Quick Start.


, — . — command prompt. , , — , token. null-safe lateinit.
.


override fun configure(transitions: StateMachineTransitionConfigurer<State, Event>) {
        transitions
                .withExternal()
                .source(State.INIT).target(State.CONNECTING)
                .event(Event.LOGIN)
                .and()
                .withExternal()
                .source(State.CONNECTING).target(State.READY).event(Event.CONNECTED)
                .action { context -> context.extendedState.variables["token"] = context.messageHeaders["token"] }.and()
                .withExternal()
                .source(State.CONNECTING).target(State.INIT).event(Event.FAILED).and()
                .withInternal()
                .source(State.READY).event(Event.COMMAND).and()
                .withExternal()
                .source(State.READY).target(State.CONNECTING).event(Event.LOGIN)
    }

....
enum class State {    INIT, CONNECTING, READY }
enum class Event {  LOGIN, CONNECTED, FAILED, COMMAND }

— , — . Spring. , , . .


. login:


    @Autowired
    lateinit var stateMachine: StateMachine<State, Event>

   //    null
    lateinit var token: String

    @ShellMethod("Authenticate and obtain token")
    fun login(
            @ShellOption name: String,
            @ShellOption password: String,
            @ShellOption(defaultValue = "admin") scope: String) =
            runBlocking {
                stateMachine.sendEvent(
                        MessageBuilder.createMessage(Event.LOGIN, MessageHeaders(mapOf("login" to name))))
                delay(1_000)
                token = UUID.randomUUID().toString()
                stateMachine.sendEvent(
                        MessageBuilder.createMessage(Event.CONNECTED, MessageHeaders(mapOf("token" to token))))
                "Connected"
            }
...

command prompt:


    private val colors = mapOf(State.READY to AttributedStyle.DEFAULT.foreground(AttributedStyle.GREEN))
            .withDefault { AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW) }

    override fun getPrompt(): AttributedString =
            AttributedString("${env}:>", colors.getValue(stateMachine.state.id))

:


    @ShellMethodAvailability("list")
    fun listAvailable() =
            if (stateMachine.state.id == State.READY) Availability.available()
            else Availability.unavailable("requires authentication performed first")

Spring State Machine — (guards), , , , , .



Spring Shell ist eine hervorragende Lösung für bestimmte Dienstprogramme für die Projektentwicklung, wenn eine teilweise Automatisierung nicht trivialer Aufgaben erforderlich ist .


Spring State Machine ist ein weiteres von vielen Community-Projekten, das mehr Aufmerksamkeit verdient und trotz Vorurteilen auf dem neuesten Stand der Technik ist.


Beispielcode ist auf GitHub verfĂĽgbar .



All Articles