בחמש השנים האחרונות היה לי את הציטוט "הכל מתחיל ברעיון" על קיר המשרד שלי.
אשתי מצאה את המוצר הזה ב-Etsy זמן קצר לאחר שהתחלתי לפתח אוסף API עבור אפליקציית כושר. אני אוהב את ההצהרה הזו כי היא לוכדת את התשוקה שצורכת אותי בשלבי היצירה של פרויקט חדש. זה עדיין ההיבט האהוב עלי בלהיות מהנדס, אפילו שלושה עשורים לתוך הקריירה שלי.
מה שלמדתי בזמן הזה הוא שרעיון חשוב רק אם למישהו יש הזדמנות לחוות אותו. אם לרעיון לוקח יותר מדי זמן להפוך למציאות, אתה בסופו של דבר עם הזדמנות שהוחמצה כשמישהו אחר מכה אותך עד הסוף. זו הסיבה שסטארט-אפים תמיד דוהרים כדי להביא את הרעיונות שלהם לשוק כמה שיותר מהר.
בואו נעבור דרך איך אנחנו יכולים להפוך רעיון למציאות... במהירות.
הנחות
עבור מאמר זה, נשמור על דברים פשוטים. נשתמש ב-Java 17 וב-Spring Boot 3 כדי ליצור ממשק API של RESTful. בדוגמה זו, נשתמש ב-Gradle עבור אוטומציית הבנייה שלנו.
בעוד שרעיון השירות שאנו מתכננים להוציא לשוק ישתמש בדרך כלל בשכבת התמדה, נשים זאת בצד לדוגמה זו ונגדיר באופן סטטי את הנתונים שלנו בתוך מחלקה של מאגר.
לא נדאג לגבי הוספת אבטחה כלשהי עבור הדוגמה הזו, פשוט נאפשר גישה אנונימית להוכחת הרעיון הזה.
ממשק ה-API של מוטיבציה ציטוטים
בואו נניח שהרעיון שלנו הוא ממשק API של מוטיבציה להצעות מחיר. כדי לוודא שאנחנו מרוצים מהר ככל האפשר, ביקשתי מ-ChatGPT ליצור עבורי מפרט OpenAPI.
תוך שניות, ChatGPT סיפק את התגובה:
הנה מפרט OpenAPI, ב-YAML, ש-ChatGPT יצר:
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
הייתי צריך רק לבצע עדכון ידני אחד - לוודא שמאפייני id
quote
נדרשים עבור סכימת Quote
. וזה רק בגלל ששכחתי לציין את האילוץ הזה ל-ChatGPT בהנחיה המקורית שלי.
עם זה, אנחנו מוכנים לפתח את השירות החדש באמצעות גישת API-First .
בניית שירות אתחול אביב באמצעות API-First
עבור דוגמה זו, אשתמש ב- Spring Boot CLI כדי ליצור פרויקט חדש. כך תוכל להתקין את ה-CLI באמצעות Homebrew:
$ brew tap spring-io/tap $ brew install spring-boot
צור שירות אתחול אביב חדש
נקרא לפרויקט quotes
, וניצור אותו בפקודה הבאה:
$ spring init --dependencies=web quotes
הבה נבחן את התוכן של תיקיית quotes
:
$ 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
לאחר מכן, אנו עורכים את קובץ build.gradle
כפי שמוצג להלן כדי לאמץ את גישת ה-API-First.
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() }
לבסוף, אנו ממקמים את מפרט ה-OpenAPI שנוצר בתיקייה resources/static
בתור openapi.yaml
.
צור את אובייקטי ה-API והמודל
לאחר פתיחת הפרויקט ב-IntelliJ, ביצעתי את הפקודה הבאה כדי לבנות את ה-API stubs ואובייקטי המודל.
./gradlew clean build
כעת, אנו יכולים לראות את אובייקטי api
model
שנוצרו ממפרט OpenAPI שלנו. הנה הקובץ QuotesAPI.java
:
הוסף את ההיגיון העסקי
כשהשירות הבסיסי מוכן וכבר עומד בחוזה ה-OpenAPI שלנו, אנחנו מתחילים להוסיף קצת היגיון עסקי לשירות.
ראשית, אנו יוצרים מחלקה QuotesRepository
אשר מחזירה את הנתונים עבור השירות שלנו. כפי שצוין לעיל, זה בדרך כלל יאוחסן בשכבת התמדה ייעודית כלשהי. עבור הדוגמה הזו, קידוד קשיח של נתונים בשווי חמישה ציטוטים עובד מצוין, וזה שומר אותנו ממוקדים.
@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)); } }
לאחר מכן, אנו יוצרים שירות QuotesService
אשר יקיים אינטראקציה עם QuotesRepository
. נקיטת גישה זו תשאיר את הנתונים נפרדים מההיגיון העסקי.
@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())); } }
לבסוף, אנחנו רק צריכים ליישם את QuotesApi
שנוצר מגישת ה-API-First שלנו:
@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); } }
בשלב זה, יש לנו ממשק API של Motivational Quotes עם אוסף קטן של תגובות.
כמה פריטים אחרונים
Spring Boot נותן לנו את האפשרות לממשק משתמש מבוסס אינטרנט של Swagger Docs באמצעות התלות springdoc-openapi-starter-webmvc-ui
.
dependencies { ... implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.5' ... }
בעוד שהמסגרת מאפשרת למהנדסים להשתמש בהערות פשוטות כדי לתאר את ה-API שלהם, אנחנו יכולים להשתמש בקובץ openapi.yaml
הקיים שלנו בתיקיה resources/static
.
אנו יכולים ליישם גישה זו בקובץ application-properties.yaml
, יחד עם כמה עדכוני תצורה קלים אחרים:
server: port: ${PORT:8080} spring: application: name: quotes springdoc: swagger-ui: path: /swagger-docs url: openapi.yaml
סתם בשביל הכיף, בואו נוסיף קובץ banner.txt
לשימוש עם תחילת השירות. אנו מניחים את הקובץ הזה בתיקיית resources
.
${AnsiColor.BLUE} _ __ _ _ _ ___ | |_ ___ ___ / _` | | | |/ _ \| __/ _ \/ __| | (_| | |_| | (_) | || __/\__ \ \__, |\__,_|\___/ \__\___||___/ |_| ${AnsiColor.DEFAULT} :: Running Spring Boot ${AnsiColor.BLUE}${spring-boot.version}${AnsiColor.DEFAULT} :: Port #${AnsiColor.BLUE}${server.port}${AnsiColor.DEFAULT} ::
כעת, כאשר אנו מתחילים את השירות באופן מקומי, אנו יכולים לראות את הבאנר:
לאחר שהתחלנו, נוכל לאמת ש-Swagger Docs פועלים על ידי ביקור בנקודת הקצה /swagger-docs
.
לבסוף, ניצור מאגר חדש מבוסס Git כדי שנוכל לעקוב אחר כל שינוי עתידי:
$ git init $ git add . $ git commit -m "Initial commit for the Motivational Quotes API"
עכשיו, בואו נראה באיזו מהירות אנחנו יכולים לפרוס את השירות שלנו .
שימוש בהרוקו כדי לסיים את המסע
עד כה, המוקד העיקרי להצגת הרעיון החדש שלי היה יצירת מפרט OpenAPI וכתיבת היגיון עסקי כלשהו עבור השירות שלי. Spring Boot טיפל בכל השאר בשבילי.
כשזה מגיע להפעלת השירות שלי, אני מעדיף להשתמש ב-Heroku מכיוון שהוא מתאים מאוד לשירותי Spring Boot. אני יכול לפרוס את השירותים שלי במהירות מבלי להסתבך עם חששות של תשתית ענן. Heroku גם מקל על העברת ערכי תצורה עבור היישומים מבוססי Java שלי .
כדי להתאים את גרסת ה-Java בה אנו משתמשים, אנו יוצרים קובץ system.properties
בתיקיית השורש של הפרויקט. לקובץ יש שורה אחת:
java.runtime.version = 17
לאחר מכן, אני יוצר Procfile
באותו מיקום להתאמה אישית של התנהגות הפריסה. לקובץ הזה יש גם שורה אחת:
web: java -jar build/libs/quotes-0.0.1-SNAPSHOT.jar
הגיע הזמן לפרוס. עם Heroku CLI , אני יכול לפרוס את השירות באמצעות כמה פקודות פשוטות. ראשית, אני מאמת את ה-CLI ואז יוצר אפליקציית Heroku חדשה.
$ 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
מופע אפליקציית Heroku שלי נקרא vast-crag-43256
(יכולתי לעבור בשם מסוים), והשירות יפעל בכתובת https://vast-crag-43256-bb5e35ea87de.herokuapp.com/.
הדבר האחרון שצריך לעשות הוא לפרוס את השירות באמצעות פקודת Git כדי לדחוף את הקוד ל-Heroku:
$ git push heroku master
לאחר השלמת פקודה זו, נוכל לאמת פריסה מוצלחת באמצעות לוח המחוונים של Heroku:
עכשיו, אנחנו מוכנים לקחת את השירות החדש שלנו לנסיעת מבחן!
ציטוטים מוטיבציוניים בפעולה
עם שירות מוטיבציית ציטוטים הפועל על Heroku, אנו יכולים לאמת שהכל עובד כמצופה באמצעות סדרה של פקודות curl
.
ראשית, בואו נקבל רשימה מלאה של כל חמשת הציטוטים המניעים:
$ 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." } ]
בוא נשחזר ציטוט מוטיבציה יחיד לפי תעודת זהות:
$ 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." }
בואו לקבל ציטוט אקראי של מוטיבציה:
$ 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." }
אנחנו יכולים אפילו לעיין ב- Swagger Docs.
מַסְקָנָה
זמן שיווק יכול ליצור או לשבור כל רעיון. זו הסיבה שסטארט-אפים מתמקדים בלייזר באספקת החידושים שלהם במהירות האפשרית. ככל שייקח יותר זמן להגיע לקו הסיום, כך גדל הסיכון שמתחרה יגיע לפניך.
הקוראים שלי עשויים להיזכר בהצהרת המשימה האישית שלי, שלדעתי יכולה ליישם על כל איש מקצוע בתחום ה-IT:
"מקד את זמנך באספקת תכונות/פונקציונליות שמרחיבה את הערך של הקניין הרוחני שלך. נצל מסגרות, מוצרים ושירותים לכל השאר."
- ג'יי וסטר
במאמר זה ראינו כיצד Spring Boot טיפל בכל מה שנדרש כדי ליישם ממשק API של RESTful. באמצעות מינוף ChatGPT, הצלחנו אפילו לבטא במילים אנושיות את מה שרצינו שהשירות שלנו יהיה, וזה יצר עבורנו מפרט OpenAPI תוך שניות. זה איפשר לנו למנף גישת API-First. לאחר שהכנו, הצלחנו להעביר את הרעיון שלנו באמצעות Heroku על ידי הוצאת כמה פקודות CLI.
Spring Boot, ChatGPT והרוקו סיפקו את המסגרות והשירותים כדי שאוכל להישאר ממוקד לייזר במימוש הרעיון שלי. כתוצאה מכך, הצלחתי לדבוק בהצהרת המשימה האישית שלי, וחשוב מכך, להעביר את הרעיון שלי במהירות. כל מה שהייתי צריך לעשות זה להתמקד בלוגיקה העסקית שמאחורי הרעיון שלי - וככה זה צריך להיות!
אם אתה מעוניין, את קוד המקור למאמר זה ניתן למצוא ב- GitLab .
שיהיה לך יום נהדר באמת!