다음 튜토리얼에서는 Google 프로토콜 버퍼 (protobuf) 정의를 사용하여 JSON으로 피드를 빌드하는 방법을 보여줍니다. protobuf 컴파일러를 사용하여 protobuf 스키마를 기반으로 소스 코드를 생성합니다. 생성된 소스 코드를 사용하여 피드를 빌드하면 간편한 인터페이스가 제공되고 잘못된 필드 이름이나 필드 유형으로 피드 항목이 생성되지 않습니다.
프로젝트 설정
- 새 프로젝트 디렉터리를 만듭니다.
- proto definitions에서 offer.proto money.proto dayofweek.proto timeofday.proto date.proto의 콘텐츠를 프로젝트 디렉터리 루트에 새 파일로 복사합니다.
- protoc 컴파일러 설치
.proto 파일에서 소스 코드를 생성하려면 protoc 컴파일러가 필요합니다. Protocol Buffers GitHub 출시 페이지에서 최신 사전 빌드된 바이너리를 다운로드합니다.
zip 파일을 추출하고 PATH 환경 변수에 bin 경로를 추가합니다. 예를 들면 다음과 같습니다.
unzip protoc-22.0-linux-x86_64.zip -d /usr/local/protoc export PATH="$PATH:/usr/local/protoc/bin"
소스 코드 생성
Python
- Python protobuf 라이브러리 설치
pip install protobuf
- 클라이언트 소스 코드를 생성합니다.
proto 출력 디렉터리 생성: generated
protoc --python_out=./generated offer.proto money.proto dayofweek.proto timeofday.proto date.proto
생성된 경로를 포함하도록 PYTHONPATH 환경 변수를 업데이트합니다. 예를 들면 다음과 같습니다.
export PYTHONPATH="$PYTHONPATH:./generated"
Bazel을 사용하는 경우 protoc를 실행하는 대신 py_proto_library 규칙을 사용해 보세요.
사용 예
전체 예시 프로젝트는 여기에서 확인할 수 있습니다.
import json from google.protobuf.json_format import MessageToDict from google.protobuf.timestamp_pb2 import Timestamp # Replace these imports with your actual generated proto package paths from generated import offer_pb2 from generated import money_pb2 from generated import dayofweek_pb2 from generated import timeofday_pb2 _MAX_BYTES_DATA_FILE = 200 * 1024 * 1024 def generate_offer_feed(): # Create OfferFeed feed = offer_pb2.OfferFeed() # Build the offers offer = offer_pb2.Offer( offer_id="offer-1", entity_ids=["dining-1"], offer_source=offer_pb2.OFFER_SOURCE_AGGREGATOR, action_type=offer_pb2.ACTION_TYPE_DINING, offer_modes=[ offer_pb2.OFFER_MODE_WALK_IN, offer_pb2.OFFER_MODE_FREE_RESERVATION ], offer_category=offer_pb2.OFFER_CATEGORY_BASE_OFFER, offer_details=offer_pb2.OfferDetails( offer_display_text="₹100 off on your order", # Note: If this is a 'oneof', you set the field name directly discount_value=money_pb2.Money( currency_code="INR", units=100 ) ), offer_restrictions=offer_pb2.OfferRestrictions( combinable_with_other_offers=True, combinable_offer_categories=[ offer_pb2.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER, offer_pb2.OFFER_CATEGORY_ADD_ON_COUPON_OFFER ] ), terms=offer_pb2.Terms( restricted_to_certain_users=False, terms_and_conditions="Valid on all menu items." ), validity_periods=[ offer_pb2.ValidityPeriod( valid_period=offer_pb2.ValidityRange( valid_from_time=Timestamp(seconds=1687062000), valid_through_time=Timestamp(seconds=1956556800) ), time_of_day=[ # Monday - Thursday Window offer_pb2.TimeOfDayWindow( time_windows=offer_pb2.TimeOfDayRange( open_time=timeofday_pb2.TimeOfDay(hours=13), close_time=timeofday_pb2.TimeOfDay(hours=23) ), day_of_week=[ dayofweek_pb2.DayOfWeek.MONDAY, dayofweek_pb2.DayOfWeek.TUESDAY, dayofweek_pb2.DayOfWeek.WEDNESDAY, dayofweek_pb2.DayOfWeek.THURSDAY ] ), # Friday - Sunday Window offer_pb2.TimeOfDayWindow( time_windows=offer_pb2.TimeOfDayRange( open_time=timeofday_pb2.TimeOfDay(hours=13), close_time=timeofday_pb2.TimeOfDay(hours=23, minutes=59, seconds=59) ), day_of_week=[ dayofweek_pb2.DayOfWeek.FRIDAY, dayofweek_pb2.DayOfWeek.SATURDAY, dayofweek_pb2.DayOfWeek.SUNDAY ] ) ] ) ], offer_url="https://www.example-restaurant.com/offer/base_offer_1", image_url="https://www.example-restaurant.com/images/offer_base.jpg" ) # Example testing for menu feed size # Protocol buffer message must be less than 2 GiB # https://protobuf.dev/programming-guides/proto-limits/ # It is recommended to not exceed 200 MB, as there is an Actions # Center limit of 200 MB per file after compression. if feed.ByteSize() + offer.ByteSize() < _MAX_BYTES_DATA_FILE: feed.data.append(offer) # else write current feed to file and start a new feed # Serialize to JSON # preserving_proto_field_names=True ensures camelCase becomes snake_case if defined that way in .proto json_output = json.dumps( MessageToDict(feed, preserving_proto_field_name=True) ) print(json_output) if __name__ == "__main__": generate_offer_feed()
이 코드 예에서는 DINING 혜택 피드를 만드는 방법을 보여줍니다. 그런 다음 피드를 JSON으로 직렬화하는 방법을 보여줍니다.
Python API를 사용하면 속성을 설정하여 중첩된 객체를 지연 초기화할 수 있습니다.자바
- 여기에 설명된 대로 Maven 또는 Gradle을 사용하여 프로젝트에 protobuf-java 및 protobuf-java-util 종속 항목을 추가합니다.
- 클라이언트 소스 코드를 생성합니다.
protoc --java_out=src/main/java offer.proto money.proto dayofweek.proto timeofday.proto date.proto
Maven을 사용하는 경우 protobuf-maven-plugin을 사용하여 컴파일 시간에 소스 코드를 생성할 수 있습니다.
Bazel을 사용하는 경우 protoc를 실행하는 대신 java_proto_library 규칙을 사용해 보세요.
사용 예
전체 예시 프로젝트는 여기에서 확인할 수 있습니다.
package com.example.offers; import com.google.protobuf.util.JsonFormat; import ext.maps.booking.feeds.offers.Offer; import ext.maps.booking.feeds.offers.OfferFeed; import ext.maps.booking.feeds.offers.OfferDetails; import ext.maps.booking.feeds.offers.OfferRestrictions; import ext.maps.booking.feeds.offers.Terms; import ext.maps.booking.feeds.offers.ValidityPeriod; import ext.maps.booking.feeds.offers.ValidityRange; import ext.maps.booking.feeds.offers.OfferCategory; import ext.maps.booking.feeds.offers.OfferMode; import ext.maps.booking.feeds.offers.OfferSource; import ext.maps.booking.feeds.offers.ActionType; import ext.maps.booking.feeds.offers.TimeOfDayWindow; import ext.maps.booking.feeds.offers.TimeOfDayRange; import com.google.type.Money; import com.google.protobuf.Timestamp; import com.google.type.DayOfWeek; import com.google.type.TimeOfDay; public class OfferFeedGenerator { // 200 MB public static final int MAX_BYTES_DATA_FILE = 200 * 1024 * 1024; public static void main(String[] args) throws Exception { // Create OfferFeed OfferFeed.Builder feed = OfferFeed.newBuilder(); // Build the offers Offer offer = Offer.newBuilder() .setOfferId("offer-1") .addEntityIds("dining-1") .setOfferSource(OfferSource.OFFER_SOURCE_AGGREGATOR) .setActionType(ActionType.ACTION_TYPE_DINING) .addOfferModes(OfferMode.OFFER_MODE_WALK_IN) .addOfferModes(OfferMode.OFFER_MODE_FREE_RESERVATION) .setOfferCategory(OfferCategory.OFFER_CATEGORY_BASE_OFFER) .setOfferDetails(OfferDetails.newBuilder() .setOfferDisplayText("₹100 off on your order") .setDiscountValue(Money.newBuilder() .setCurrencyCode("INR") .setUnits(100))) .setOfferRestrictions(OfferRestrictions.newBuilder() .setCombinableWithOtherOffers(true) .addCombinableOfferCategories(OfferCategory.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER) .addCombinableOfferCategories(OfferCategory.OFFER_CATEGORY_ADD_ON_COUPON_OFFER)) .setTerms(Terms.newBuilder() .setRestrictedToCertainUsers(false) .setTermsAndConditions("Valid on all menu items.")) .addValidityPeriods(ValidityPeriod.newBuilder() .setValidPeriod(ext.maps.booking.feeds.offers.ValidityRange.newBuilder() .setValidFromTime(Timestamp.newBuilder().setSeconds(1687062000)) .setValidThroughTime(Timestamp.newBuilder().setSeconds(1956556800))) // // Monday - Thursday Window .addTimeOfDay(ext.maps.booking.feeds.offers.TimeOfDayWindow.newBuilder() .setTimeWindows(ext.maps.booking.feeds.offers.TimeOfDayRange.newBuilder() .setOpenTime(com.google.type.TimeOfDay.newBuilder().setHours(13)) .setCloseTime(com.google.type.TimeOfDay.newBuilder().setHours(23))) .addDayOfWeek(DayOfWeek.MONDAY) .addDayOfWeek(DayOfWeek.TUESDAY) .addDayOfWeek(DayOfWeek.WEDNESDAY) .addDayOfWeek(DayOfWeek.THURSDAY)) // Friday - Sunday Window .addTimeOfDay(ext.maps.booking.feeds.offers.TimeOfDayWindow.newBuilder() .setTimeWindows(ext.maps.booking.feeds.offers.TimeOfDayRange.newBuilder() .setOpenTime(com.google.type.TimeOfDay.newBuilder().setHours(13)) .setCloseTime(com.google.type.TimeOfDay.newBuilder().setHours(23).setMinutes(59).setSeconds(59))) .addDayOfWeek(DayOfWeek.FRIDAY) .addDayOfWeek(DayOfWeek.SATURDAY) .addDayOfWeek(DayOfWeek.SUNDAY)) ) .setOfferUrl("https://www.example-restaurant.com/offer/base_offer_1") .setImageUrl("https://www.example-restaurant.com/images/offer_base.jpg") .build(); // Example testing for offer feed size // Protocol buffer message must be less than 2 GiB // https://protobuf.dev/programming-guides/proto-limits/ // It is recommended to not exceed 200 MB, as there is an Actions // Center limit of 200 MB per file after compression. int offerSize = offer.getSerializedSize(); int currentFeedSize = feed.build().getSerializedSize(); if (currentFeedSize + offerSize < MAX_BYTES_DATA_FILE) { feed.addData(offer); } else { // 1. Serialize and save the current 'feed' to a file // 2. Reset 'feed' to a new empty OfferFeed // 3. Add the 'offer' to the new feed } // Serialize to JSON String jsonOutput = JsonFormat.printer() .preservingProtoFieldNames() .print(feed); System.out.println(jsonOutput); } }
이 코드 예에서는 DINING 혜택 피드를 만드는 방법을 보여줍니다. 그런 다음 피드를 JSON으로 직렬화하는 방법을 보여줍니다.
Go
- Go용 protoc 플러그인 설치
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
protoc-gen-go 플러그인을 포함하도록 PATH 환경 변수를 업데이트합니다. 예를 들면 다음과 같습니다.
export PATH="$PATH:$(go env GOPATH)/bin"
- 앱을 초기화하고 클라이언트 소스 코드를 생성합니다.
go mod init feed/app mkdir generated protoc --go_out=./generated/ offer.proto money.proto dayofweek.proto timeofday.proto date.proto
Bazel을 사용하는 경우 protoc를 실행하는 대신 go_proto_library 규칙을 사용해 보세요.
사용 예
전체 예시 프로젝트는 여기에서 확인할 수 있습니다.
package main import ( pb "feed/app/generated/ext/maps/booking/offers/proto" "fmt" "log" money "google.golang.org/genproto/googleapis/type/money" timestamppb "google.golang.org/protobuf/types/known/timestamppb" dayofweek "google.golang.org/genproto/googleapis/type/dayofweek" timeofday "google.golang.org/genproto/googleapis/type/timeofday" proto "google.golang.org/protobuf/proto" "google.golang.org/protobuf/encoding/protojson" ) func main() { // 200 MB feed file size limit const MaxBytesDataFile = 200 * 1024 * 1024 // Create OfferFeed with offers feed := &pb.OfferFeed{} // Build the offers offer := &pb.Offer{ OfferId: "offer-1", EntityIds: []string{"dining-1"}, OfferSource: pb.OfferSource_OFFER_SOURCE_AGGREGATOR, ActionType: pb.ActionType_ACTION_TYPE_DINING, OfferModes: []pb.OfferMode{ pb.OfferMode_OFFER_MODE_WALK_IN, pb.OfferMode_OFFER_MODE_FREE_RESERVATION, }, OfferCategory: pb.OfferCategory_OFFER_CATEGORY_BASE_OFFER, OfferDetails: &pb.OfferDetails{ OfferDisplayText: "₹100 off on your order", OfferSpecification: &pb.OfferDetails_DiscountValue{ DiscountValue: &money.Money{ CurrencyCode: "INR", Units: 100, }, }, }, OfferRestrictions: &pb.OfferRestrictions{ CombinableWithOtherOffers: true, CombinableOfferCategories: []pb.OfferCategory{ pb.OfferCategory_OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER, pb.OfferCategory_OFFER_CATEGORY_ADD_ON_COUPON_OFFER, }, }, Terms: &pb.Terms{ RestrictedToCertainUsers: false, TermsAndConditions: "Valid on all menu items.", }, ValidityPeriods: []*pb.ValidityPeriod{ { ValidPeriod: &pb.ValidityRange{ ValidFromTime: ×tamppb.Timestamp{Seconds: 1687062000}, ValidThroughTime: ×tamppb.Timestamp{Seconds: 1956556800}, }, TimeOfDay: []*pb.TimeOfDayWindow{ // Monday - Thursday Window { TimeWindows: &pb.TimeOfDayRange{ OpenTime: &timeofday.TimeOfDay{Hours: 13}, CloseTime: &timeofday.TimeOfDay{Hours: 23}, }, DayOfWeek: []dayofweek.DayOfWeek{ dayofweek.DayOfWeek_MONDAY, dayofweek.DayOfWeek_TUESDAY, dayofweek.DayOfWeek_WEDNESDAY, dayofweek.DayOfWeek_THURSDAY, }, }, // Friday - Sunday Window { TimeWindows: &pb.TimeOfDayRange{ OpenTime: &timeofday.TimeOfDay{Hours: 13}, CloseTime: &timeofday.TimeOfDay{ Hours: 23, Minutes: 59, Seconds: 59, }, }, DayOfWeek: []dayofweek.DayOfWeek{ dayofweek.DayOfWeek_FRIDAY, dayofweek.DayOfWeek_SATURDAY, dayofweek.DayOfWeek_SUNDAY, }, }, }, }, }, OfferUrl: "https://www.example-restaurant.com/offer/base_offer_1", ImageUrl: "https://www.example-restaurant.com/images/offer_base.jpg", } // Example testing for feed size // Protocol buffer message must be less than 2 GiB // https://protobuf.dev/programming-guides/proto-limits/ // It is recommended to not exceed 200 MB, as there is an Actions // Center limit of 200 MB per file after compression. offerSize := proto.Size(offer) currentFeedSize := proto.Size(feed) if currentFeedSize + offerSize < MaxBytesDataFile { feed.Data = append(feed.Data, offer) } else { // 1. Serialize and save the current 'feed' to a file // 2. Reset 'feed' to a new empty OfferFeed // 3. Add the 'offer' to the new feed } // Serialize to JSON marshaler := protojson.MarshalOptions{ UseProtoNames: true, } jsonOutput, err := marshaler.Marshal(feed) if err != nil { log.Fatalf("Failed to marshal feed: %v", err) } fmt.Println(string(jsonOutput)) }
이 코드 예에서는 DINING 혜택 피드를 만드는 방법을 보여줍니다. 그런 다음 피드를 JSON으로 직렬화하는 방법을 보여줍니다.
TypeScript
-
TypeScript protoc 플러그인을 설치합니다.
ts-proto는 공식적으로 지원되는 Google 프로젝트가 아닙니다.npm init npm i -D typescript npm i ts-proto
- 출력 디렉터리를 만들고 클라이언트 소스 코드를 생성합니다.
proto 출력 디렉터리 src/generated를 만듭니다.
protoc --plugin="./node_modules/.bin/protoc-gen-ts_proto" --ts_proto_opt=useOptionals=all --ts_proto_opt=snakeToCamel=false --ts_proto_opt=onlyTypes=true --ts_proto_out="./src/generated" offer.proto money.proto dayofweek.proto timeofday.proto date.proto
Bazel을 사용하는 경우 protoc를 실행하는 대신 js_proto_library 규칙을 사용해 보세요.
사용 예
전체 예시 프로젝트는 여기에서 확인할 수 있습니다.
import { Offer, OfferFeed, OfferSource, ActionType, OfferMode, OfferCategory } from "./generated/offer"; // Path to your generated types import { Money } from "./generated/money"; import { DayOfWeek } from "./generated/dayofweek"; import { TimeOfDay } from "./generated/timeofday"; import { Timestamp } from "./generated/google/protobuf/timestamp"; import { Duration } from "./generated/google/protobuf/duration"; // 200 MB Limit const MAX_BYTES_DATA_FILE = 200 * 1024 * 1024; function generateOfferFeed() { // Build the Offer object const offer: Offer = { offer_id: "offer-1", entity_ids: ["dining-1"], offer_source: OfferSource.OFFER_SOURCE_AGGREGATOR, action_type: ActionType.ACTION_TYPE_DINING, offer_modes: [ OfferMode.OFFER_MODE_WALK_IN, OfferMode.OFFER_MODE_FREE_RESERVATION, ], offer_category: OfferCategory.OFFER_CATEGORY_BASE_OFFER, offer_details: { offer_display_text: "₹100 off on your order", // Set 'oneof' field: discountValue discount_value: { currency_code: "INR", units: 100, }, }, offer_restrictions: { combinable_with_other_offers: true, combinable_offer_categories: [ OfferCategory.OFFER_CATEGORY_ADD_ON_PAYMENT_OFFER, OfferCategory.OFFER_CATEGORY_ADD_ON_COUPON_OFFER, ], }, terms: { restricted_to_certain_users: false, terms_and_conditions: "Valid on all menu items.", }, validity_periods: [ { valid_period: { valid_from_time: new Date(1687062000000), valid_through_time: new Date(1956556800000), }, time_of_day: [ // Monday - Thursday Window { time_windows: { open_time: { hours: 13, minutes: 0, seconds: 0, nanos: 0 }, close_time: { hours: 23, minutes: 0, seconds: 0, nanos: 0 }, }, day_of_week: [ DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, ], }, // Friday - Sunday Window { time_windows: { open_time: { hours: 13, minutes: 0, seconds: 0, nanos: 0 }, close_time: { hours: 23, minutes: 59, seconds: 59, nanos: 0 }, }, day_of_week: [ DayOfWeek.FRIDAY, DayOfWeek.SATURDAY, DayOfWeek.SUNDAY, ], }, ], }, ], offer_url: "https://www.example-restaurant.com/offer/base_offer_1", image_url: "https://www.example-restaurant.com/images/offer_base.jpg", }; // 2. Initialize the Feed const feed: OfferFeed = { data: [] }; // 3. Size checking // encode().finish() returns a Uint8Array (the binary representation) const offerSize = new Blob([JSON.stringify(offer)]).size; const currentFeedSize = new Blob([JSON.stringify(feed)]).size; if (currentFeedSize + offerSize < MAX_BYTES_DATA_FILE) { if (feed.data == undefined) { feed.data = [offer]; } else { feed.data.push(offer); } } else { // Logic for file rotation goes here console.log("Feed size limit reached. Rotate file."); } // 4. Serialize to JSON // In JS/TS, we often just use JSON.stringify for the plain object // if you used proto3 and want to ensure proper enum/timestamp formatting, // use the library's toJSON method. const jsonOutput = JSON.stringify(feed); console.log(jsonOutput); } generateOfferFeed();
이 코드 예에서는 DINING 혜택 피드를 만드는 방법을 보여줍니다. 그런 다음 피드를 JSON으로 직렬화하는 방법을 보여줍니다.
참고: 서드 파티 protoc 컴파일러 확장 프로그램을 사용하여 proto 스키마에서 JSON 스키마를 생성할 수 있습니다. JSON 스키마 파일로 작업하려면 여기에서 예를 검토하세요.