Skip to content

Commit 83484c1

Browse files
committed
feat: add example service with storage implementation
1 parent b2b103e commit 83484c1

File tree

5 files changed

+232
-12
lines changed

5 files changed

+232
-12
lines changed

examples/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# AEP-Go Example Service
2+
3+
This example demonstrates how to use the AEP-Go library to build a gRPC service that follows AEP standards. The service implements:
4+
5+
- Resource path validation and parsing
6+
- Resource ID generation
7+
- Pagination with token-based validation
8+
9+
## Service Overview
10+
11+
The example implements a simple Book Service with the following operations:
12+
- `CreateBook`: Creates a new book with an optional or auto-generated ID
13+
- `GetBook`: Retrieves a book by its resource path
14+
- `ListBooks`: Lists books with pagination support
15+
16+
## Project Structure
17+
18+
```
19+
.
20+
├── service.go # Service implementation
21+
└── go.mod # Go module file
22+
```

examples/service.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package examples
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/blaberg/aep-go/pagination"
8+
bookv1 "github.com/blaberg/aep-go/proto/gen/example/books/v1"
9+
"github.com/blaberg/aep-go/resourceid"
10+
"github.com/blaberg/aep-go/resourcepath"
11+
"github.com/blaberg/aep-go/validate"
12+
"google.golang.org/grpc/codes"
13+
"google.golang.org/grpc/status"
14+
"google.golang.org/protobuf/types/known/timestamppb"
15+
)
16+
17+
// Service implements the BookService
18+
type Service struct {
19+
paginator *pagination.Paginator
20+
storage *Storage
21+
}
22+
23+
// CreateBook implements the CreateBook RPC
24+
func (s *Service) CreateBook(ctx context.Context, req *bookv1.CreateBookRequest) (*bookv1.Book, error) {
25+
// Generate book ID if not provided
26+
bookID := req.Id
27+
if bookID == "" {
28+
bookID = resourceid.New()
29+
}
30+
if err := validate.ResourceID(bookID); err != nil {
31+
return nil, status.Errorf(codes.InvalidArgument, "invalid book ID: %v", err)
32+
}
33+
34+
// Create the book's resource path
35+
path := bookv1.NewBookPath(bookID)
36+
37+
// Create the book
38+
now := timestamppb.New(time.Now())
39+
book := &bookv1.Book{
40+
Path: path.String(),
41+
DisplayName: req.Book.DisplayName,
42+
CreateTime: now,
43+
UpdateTime: now,
44+
}
45+
46+
// Store the book
47+
s.storage.Create(book)
48+
49+
return book, nil
50+
}
51+
52+
// GetBook implements the GetBook RPC
53+
func (s *Service) GetBook(ctx context.Context, req *bookv1.GetBookRequest) (*bookv1.Book, error) {
54+
// Validate path format
55+
path, err := bookv1.ParseBookResourcePath(req.GetPath())
56+
if err != nil {
57+
return nil, status.Errorf(codes.InvalidArgument, "invalid resource path format: %v", err)
58+
}
59+
60+
// Get the book
61+
book, ok := s.storage.Get(path.GetBook())
62+
if !ok {
63+
return nil, status.Errorf(codes.NotFound, "book not found: %s", req.Path)
64+
}
65+
66+
return book, nil
67+
}
68+
69+
// ListBooks implements the ListBooks RPC
70+
func (s *Service) ListBooks(ctx context.Context, req *bookv1.ListBooksRequest) (*bookv1.ListBooksResponse, error) {
71+
// Validate parent format
72+
_, err := resourcepath.ParseString(req.Parent, "publishers/{publisher}")
73+
if err != nil {
74+
return nil, status.Errorf(codes.InvalidArgument, "invalid parent format: %v", err)
75+
}
76+
77+
// Parse the page token
78+
token, err := s.paginator.ParsePageToken(req)
79+
if err != nil {
80+
return nil, status.Errorf(codes.InvalidArgument, "invalid page token: %v", err)
81+
}
82+
83+
// Get books with pagination
84+
books, hasMore := s.storage.List(req.Parent, token.Offset, req.MaxPageSize)
85+
if books == nil {
86+
return &bookv1.ListBooksResponse{
87+
Results: []*bookv1.Book{},
88+
NextPageToken: "",
89+
}, nil
90+
}
91+
92+
// Generate the next page token
93+
nextToken := token.Next(hasMore, req.MaxPageSize)
94+
nextPageToken := ""
95+
if nextToken != nil {
96+
nextPageToken = nextToken.String()
97+
}
98+
99+
return &bookv1.ListBooksResponse{
100+
Results: books,
101+
NextPageToken: nextPageToken,
102+
}, nil
103+
}

examples/storage.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package examples
2+
3+
import (
4+
"sync"
5+
6+
bookv1 "github.com/blaberg/aep-go/proto/gen/example/books/v1"
7+
)
8+
9+
// Storage provides in-memory storage for books
10+
type Storage struct {
11+
mu sync.RWMutex
12+
books []*bookv1.Book
13+
}
14+
15+
// NewStorage creates a new storage instance
16+
func NewStorage() *Storage {
17+
return &Storage{
18+
books: make([]*bookv1.Book, 0),
19+
}
20+
}
21+
22+
// Create stores a new book
23+
func (s *Storage) Create(book *bookv1.Book) {
24+
s.mu.Lock()
25+
defer s.mu.Unlock()
26+
s.books = append(s.books, book)
27+
}
28+
29+
// Get retrieves a book by its path
30+
func (s *Storage) Get(path string) (*bookv1.Book, bool) {
31+
s.mu.RLock()
32+
defer s.mu.RUnlock()
33+
34+
for _, book := range s.books {
35+
if book.Path == path {
36+
return book, true
37+
}
38+
}
39+
return nil, false
40+
}
41+
42+
// List returns books that match the given parent path with pagination
43+
func (s *Storage) List(parent string, offset int64, pageSize int32) ([]*bookv1.Book, bool) {
44+
s.mu.RLock()
45+
defer s.mu.RUnlock()
46+
47+
var filteredBooks []*bookv1.Book
48+
for _, book := range s.books {
49+
if book.Path[:len(parent)] == parent {
50+
filteredBooks = append(filteredBooks, book)
51+
}
52+
}
53+
54+
// Apply pagination
55+
start := int(offset)
56+
end := start + int(pageSize)
57+
if end > len(filteredBooks) {
58+
end = len(filteredBooks)
59+
}
60+
if start >= len(filteredBooks) {
61+
return nil, false
62+
}
63+
64+
// Check if there are more pages
65+
hasMore := end < len(filteredBooks)
66+
67+
return filteredBooks[start:end], hasMore
68+
}
69+
70+
// Delete removes a book by its path
71+
func (s *Storage) Delete(path string) {
72+
s.mu.Lock()
73+
defer s.mu.Unlock()
74+
75+
for i, book := range s.books {
76+
if book.Path == path {
77+
// Remove the book by swapping with the last element and truncating
78+
s.books[i] = s.books[len(s.books)-1]
79+
s.books = s.books[:len(s.books)-1]
80+
return
81+
}
82+
}
83+
}

go.mod

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ module github.com/blaberg/aep-go
22

33
go 1.23
44

5-
require github.com/google/go-cmp v0.6.0
6-
75
require (
8-
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9
9-
google.golang.org/protobuf v1.35.1
6+
github.com/google/go-cmp v0.6.0
7+
github.com/google/uuid v1.6.0
8+
golang.org/x/text v0.22.0
9+
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a
10+
google.golang.org/grpc v1.72.0
11+
google.golang.org/protobuf v1.36.5
1012
gotest.tools/v3 v3.5.1
1113
)
1214

1315
require (
14-
github.com/google/uuid v1.6.0
15-
golang.org/x/text v0.17.0
16+
golang.org/x/sys v0.30.0 // indirect
17+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
1618
)

go.sum

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
1+
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
2+
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
13
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
24
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
35
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
46
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5-
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
6-
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
7-
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
8-
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
9-
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
10-
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
7+
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
8+
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
9+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
10+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
11+
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
12+
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
13+
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
14+
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
15+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
16+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
17+
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
18+
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
19+
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
20+
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
1121
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
1222
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

0 commit comments

Comments
 (0)