Az elmúlt öt évben az irodám falán ott volt a „minden egy ötlettel kezdődik” idézet.
A feleségem nem sokkal azután találta meg ezt a terméket az Etsy-n, hogy elkezdtem API-gyűjteményt fejleszteni egy fitneszalkalmazáshoz. Szeretem ezt a kijelentést, mert megragadja azt a szenvedélyt, amely egy új projekt létrehozási szakaszaiban felemészt. Még mindig ez a kedvenc szempontom mérnökként, még három évtizede a karrierem során.
Amit megtanultam ezalatt az az, hogy egy ötlet csak akkor számít, ha valakinek lehetősége van megtapasztalni. Ha egy ötlet túl sokáig tart, hogy valósággá váljon, akkor egy elszalasztott lehetőséghez jut, amikor valaki más üti meg. Ez az oka annak, hogy a startupok mindig versenyeznek, hogy ötleteiket a lehető leggyorsabban piacra vigyék.
Nézzük végig, hogyan valósíthatunk meg egy ötletet… gyorsan.
Ebben a cikkben a dolgokat egyszerűnek tartjuk. Java 17-et és Spring Boot 3-at használunk a RESTful API létrehozásához. Ebben a példában a Gradle-t fogjuk használni az építési automatizáláshoz.
Míg a piacra vinni tervezett szolgáltatási ötlet általában perzisztencia réteget használ, ezt a példában félretesszük, és statikusan meghatározzuk adatainkat egy tárosztályon belül.
Nem fogunk aggódni amiatt, hogy ehhez a példához adjunk hozzá semmilyen biztonságot, egyszerűen engedélyezzük a névtelen hozzáférést ehhez a koncepcióhoz.
Tegyük fel, hogy ötletünk egy motivációs idézet API. Annak érdekében, hogy a lehető leggyorsabban versenyezzünk, megkértem a ChatGPT-t, hogy készítsen nekem egy OpenAPI specifikációt.
A ChatGPT másodperceken belül megadta a választ:
Íme az OpenAPI specifikáció a YAML-ben, amelyet a ChatGPT generált:
openapi: 3.0.0 info: title: Motivational Quotes API description: An API that provides motivational quotes. version: 1.0.0 servers: - url: https://api.example.com description: Production server paths: /quotes: get: summary: Get all motivational quotes operationId: getAllQuotes responses: '200': description: A list of motivational quotes content: application/json: schema: type: array items: $ref: '#/components/schemas/Quote' /quotes/random: get: summary: Get a random motivational quote operationId: getRandomQuote responses: '200': description: A random motivational quote content: application/json: schema: $ref: '#/components/schemas/Quote' /quotes/{id}: get: summary: Get a motivational quote by ID operationId: getQuoteById parameters: - name: id in: path required: true schema: type: integer responses: '200': description: A motivational quote content: application/json: schema: $ref: '#/components/schemas/Quote' '404': description: Quote not found components: schemas: Quote: type: object required: - id - quote properties: id: type: integer quote: type: string
Csupán egyetlen manuális frissítést kellett végrehajtanom – megbizonyosodtam arról, hogy az id
és quote
tulajdonságok szükségesek az Quote
. És ez csak azért van, mert elfelejtettem megemlíteni ezt a ChatGPT-re vonatkozó korlátozást az eredeti felszólításban.
Ezzel készen állunk az új szolgáltatás API-First megközelítéssel történő fejlesztésére.
Ebben a példában a Spring Boot parancssori felületet fogom használni egy új projekt létrehozásához. Így telepítheti a CLI-t a Homebrew segítségével:
$ brew tap spring-io/tap $ brew install spring-boot
Meghívjuk a projektet quotes
, létrehozva a következő paranccsal:
$ spring init --dependencies=web quotes
Vizsgáljuk meg az quotes
mappa tartalmát:
$ cd quotes && ls -la total 72 drwxr-xr-x@ 11 jvester 352 Mar 1 10:57 . drwxrwxrwx@ 90 jvester 2880 Mar 1 10:57 .. -rw-r--r--@ 1 jvester 54 Mar 1 10:57 .gitattributes -rw-r--r--@ 1 jvester 444 Mar 1 10:57 .gitignore -rw-r--r--@ 1 jvester 960 Mar 1 10:57 HELP.md -rw-r--r--@ 1 jvester 545 Mar 1 10:57 build.gradle drwxr-xr-x@ 3 jvester 96 Mar 1 10:57 gradle -rwxr-xr-x@ 1 jvester 8762 Mar 1 10:57 gradlew -rw-r--r--@ 1 jvester 2966 Mar 1 10:57 gradlew.bat -rw-r--r--@ 1 jvester 28 Mar 1 10:57 settings.gradle drwxr-xr-x@ 4 jvester 128 Mar 1 10:57 src
Ezután szerkesztjük a build.gradle
fájlt az alábbiak szerint, hogy alkalmazzuk az API-First megközelítést.
plugins { id 'java' id 'org.springframework.boot' version '3.4.3' id 'io.spring.dependency-management' version '1.1.7' id 'org.openapi.generator' version '7.12.0' } openApiGenerate { generatorName = "spring" inputSpec = "$rootDir/src/main/resources/static/openapi.yaml" outputDir = "$buildDir/generated" apiPackage = "com.example.api" modelPackage = "com.example.model" configOptions = [ dateLibrary: "java8", interfaceOnly: "true", useSpringBoot3: "true", useBeanValidation: "true", skipDefaultInterface: "true" ] } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.openapitools:jackson-databind-nullable:0.2.6' implementation 'io.swagger.core.v3:swagger-annotations:2.2.20' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } sourceSets { main { java { srcDirs += "$buildDir/generated/src/main/java" } } } compileJava.dependsOn tasks.openApiGenerate tasks.named('test') { useJUnitPlatform() }
Végül a generált OpenAPI specifikációt az resources/static
mappába helyezzük openapi.yaml
néven.
Miután megnyitottam a projektet IntelliJ-ben, a következő parancsot végrehajtottam az API csonkok és modellobjektumok felépítéséhez.
./gradlew clean build
Most láthatjuk az OpenAPI specifikációnkból létrehozott api
és model
. Íme a QuotesAPI.java
fájl:
Az alapszolgáltatás készenlétével és már betartva az OpenAPI-szerződésünket, elkezdjük üzleti logikával bővíteni a szolgáltatást.
Először létrehozunk egy QuotesRepository
osztályt, amely visszaadja a szolgáltatásunk adatait. Mint fentebb megjegyeztük, ezt általában valamilyen dedikált perzisztencia rétegben tárolják. Ebben a példában az öt idézetnyi adat kemény kódolása remekül működik, és megtartja a figyelmünket.
@Repository public class QuotesRepository { public static final List<Quote> QUOTES = List.of( new Quote() .id(1) .quote("The greatest glory in living lies not in never falling, but in rising every time we fall."), new Quote() .id(2) .quote("The way to get started is to quit talking and begin doing."), new Quote() .id(3) .quote("Your time is limited, so don't waste it living someone else's life."), new Quote() .id(4) .quote("If life were predictable it would cease to be life, and be without flavor."), new Quote() .id(5) .quote("If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success.") ); public List<Quote> getAllQuotes() { return QUOTES; } public Optional<Quote> getQuoteById(Integer id) { return Optional.ofNullable(QUOTES.stream().filter(quote -> quote.getId().equals(id)).findFirst().orElse(null)); } }
Ezután létrehozunk egy QuotesService
, amely együttműködik a QuotesRepository
val. Ezzel a megközelítéssel az adatok elkülönítve maradnak az üzleti logikától.
@RequiredArgsConstructor @Service public class QuotesService { private final QuotesRepository quotesRepository; public List<Quote> getAllQuotes() { return quotesRepository.getAllQuotes(); } public Optional<Quote> getQuoteById(Integer id) { return quotesRepository.getQuoteById(id); } public Quote getRandomQuote() { List<Quote> quotes = quotesRepository.getAllQuotes(); return quotes.get(ThreadLocalRandom.current().nextInt(quotes.size())); } }
Végül csak az API-First megközelítésünkből generált QuotesApi
kell megvalósítanunk:
@Controller @RequiredArgsConstructor public class QuotesController implements QuotesApi { private final QuotesService quotesService; @Override public ResponseEntity<List<Quote>> getAllQuotes() { return new ResponseEntity<>(quotesService.getAllQuotes(), HttpStatus.OK); } @Override public ResponseEntity<Quote> getQuoteById(Integer id) { return quotesService.getQuoteById(id) .map(quote -> new ResponseEntity<>(quote, HttpStatus.OK)) .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)); } @Override public ResponseEntity<Quote> getRandomQuote() { return new ResponseEntity<>(quotesService.getRandomQuote(), HttpStatus.OK); } }
Jelenleg egy teljesen működőképes Motivational Quotes API-val rendelkezünk, amely egy kis válaszgyűjteményt tartalmaz.
A Spring Boot lehetőséget ad egy webalapú Swagger Docs felhasználói felületre a springdoc-openapi-starter-webmvc-ui
függőségen keresztül.
dependencies { ... implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5' ... }
Míg a keretrendszer lehetővé teszi a mérnökök számára, hogy egyszerű megjegyzésekkel írják le API-jukat, használhatjuk a meglévő openapi.yaml
fájlunkat az resources/static
mappában.
Ezt a megközelítést megvalósíthatjuk az application-properties.yaml
fájlban, néhány egyéb kisebb konfigurációs frissítéssel együtt:
server: port: ${PORT:8080} spring: application: name: quotes springdoc: swagger-ui: path: /swagger-docs url: openapi.yaml
Csak szórakozásból adjunk hozzá egy banner.txt
fájlt, amelyet a szolgáltatás indításakor használhatunk. Ezt a fájlt az resources
mappába helyezzük.
${AnsiColor.BLUE} _ __ _ _ _ ___ | |_ ___ ___ / _` | | | |/ _ \| __/ _ \/ __| | (_| | |_| | (_) | || __/\__ \ \__, |\__,_|\___/ \__\___||___/ |_| ${AnsiColor.DEFAULT} :: Running Spring Boot ${AnsiColor.BLUE}${spring-boot.version}${AnsiColor.DEFAULT} :: Port #${AnsiColor.BLUE}${server.port}${AnsiColor.DEFAULT} ::
Most, amikor elindítjuk a szolgáltatást helyben, láthatjuk a bannert:
Az indítás után ellenőrizhetjük a Swagger-dokumentumok működését a /swagger-docs
végpont meglátogatásával.
Végül létrehozunk egy új Git-alapú adattárat, hogy nyomon tudjuk követni a jövőbeni változásokat:
$ git init $ git add . $ git commit -m "Initial commit for the Motivational Quotes API"
Most pedig nézzük meg, milyen gyorsan tudjuk üzembe helyezni szolgáltatásunkat .
Eddig az új ötletem bemutatásának elsődleges célja egy OpenAPI specifikáció létrehozása és néhány üzleti logika megírása volt a szolgáltatásomhoz. A Spring Boot minden mást megoldott helyettem.
Amikor a szolgáltatásomról van szó, szívesebben használom a Heroku-t, mert kiválóan illeszkedik a Spring Boot szolgáltatásokhoz. Gyorsan üzembe helyezhetem szolgáltatásaimat anélkül, hogy elakadnék a felhőinfrastruktúrával kapcsolatos problémákban. A Heroku emellett megkönnyíti a Java-alapú alkalmazásaim konfigurációs értékeinek átadását .
Az általunk használt Java verzióhoz egy system.properties
fájlt hozunk létre a projekt gyökérmappájában. A fájlnak egy sora van:
java.runtime.version = 17
Ezután ugyanazon a helyen létrehozok egy Procfile
a telepítési viselkedés testreszabásához. Ebben a fájlban is van egy sor:
web: java -jar build/libs/quotes-0.0.1-SNAPSHOT.jar
Ideje bevetni. A Heroku CLI segítségével néhány egyszerű paranccsal üzembe helyezhetem a szolgáltatást. Először hitelesítem a CLI-t, majd létrehozok egy új Heroku alkalmazást.
$ heroku login $ heroku create Creating app... done, vast-crag-43256 https://vast-crag-43256-bb5e35ea87de.herokuapp.com/ | https://git.heroku.com/vast-crag-43256.git
A Heroku alkalmazáspéldány neve vast-crag-43256
(meghatározott névvel is továbbadhattam volna), és a szolgáltatás a https://vast-crag-43256-bb5e35ea87de.herokuapp.com/ címen fog futni.
Az utolsó teendő a szolgáltatás üzembe helyezése egy Git paranccsal, hogy a kódot a Herokuba küldje:
$ git push heroku master
Ha ez a parancs elkészült, a Heroku irányítópulton keresztül ellenőrizhetjük a sikeres telepítést:
Most készen állunk arra, hogy kipróbáljuk új szolgáltatásunkat!
A Heroku-n futó Motivational Quotes szolgáltatással egy sor curl
parancs segítségével ellenőrizhetjük, hogy minden a várt módon működik-e.
Először is nézzük meg mind az öt motivációs idézet teljes listáját:
$ curl \ --location 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes'
[ { "id":1, "quote":"The greatest glory in living lies not in never falling, but in rising every time we fall." }, { "id":2, "quote":"The way to get started is to quit talking and begin doing." }, { "id":3, "quote":"Your time is limited, so don't waste it living someone else's life." }, { "id":4, "quote":"If life were predictable it would cease to be life, and be without flavor." }, { "id":5, "quote":"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success." } ]
Keressünk egyetlen motivációs idézetet azonosító alapján:
$ curl \ --location 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/3'
{ "id":3, "quote":"Your time is limited, so don't waste it living someone else's life." }
Vegyünk egy véletlenszerű motivációs idézetet:
$ curl --location \ 'https://vast-crag-43256-bb5e35ea87de.herokuapp.com/quotes/random'
{ "id":5, "quote":"If you set your goals ridiculously high and it's a failure, you will fail above everyone else's success." }
Még a Swagger-dokumentumok között is böngészhetünk.
A piacra jutás ideje bármilyen ötletet létrehozhat vagy megtörhet. Ez az oka annak, hogy a startupok lézerrel arra koncentrálnak, hogy innovációikat a lehető leggyorsabban megvalósítsák. Minél tovább tart a cél elérése, annál nagyobb a kockázata annak, hogy egy versenyző Ön előtt érkezik.
Olvasóim emlékezhetnek személyes küldetésnyilatkozatomra, amely úgy érzem, bármely informatikusra vonatkozhat:
"Fontosítsa idejét a szellemi tulajdon értékét növelő funkciók/funkcionalitás biztosítására. Használjon keretrendszereket, termékeket és szolgáltatásokat minden másra."
— J. Vester
Ebben a cikkben láthattuk, hogyan kezelte a Spring Boot mindent, ami a RESTful API megvalósításához szükséges. A ChatGPT-t kihasználva még emberi szavakkal is ki tudtuk fejezni, hogy milyennek szeretnénk szolgáltatásunkat, és pillanatok alatt elkészítette számunkra az OpenAPI specifikációt. Ez lehetővé tette számunkra, hogy kihasználjuk az API-First megközelítést. Miután készen voltunk, néhány CLI-parancs kiadásával megvalósíthattuk ötletünket a Heroku használatával.
A Spring Boot, a ChatGPT és a Heroku biztosították a keretrendszert és a szolgáltatásokat, hogy továbbra is lézerrel összpontosíthassak ötletem megvalósítására. Ennek eredményeként be tudtam tartani személyes küldetésnyilatkozatomat, és ami még fontosabb, gyorsan megvalósítottam az ötletemet. Csak az ötletem mögött meghúzódó üzleti logikára kellett összpontosítanom – és ennek így kell lennie!
Ha érdekel, a cikk forráskódja megtalálható a GitLab oldalon.
Nagyon szép napot!