Pagination
If you have millions of records in your database, you wouldn't want to display them all on one page. Doing so would be overwhelming for both the client and the server. That's why pagination is necessary.
Add pagination logic
Tune method parameters in domain/gateway/book_manager.go:
@@ -12,5 +12,5 @@ type BookManager interface {
UpdateBook(ctx context.Context, id uint, b *model.Book) error
DeleteBook(ctx context.Context, id uint) error
GetBook(ctx context.Context, id uint) (*model.Book, error)
- GetBooks(ctx context.Context) ([]*model.Book, error)
+ GetBooks(ctx context.Context, offset int) ([]*model.Book, error)
}
Add a parameter named offset
for the GetBooks
method.
Add pageSize
config item in infrastructure/config/config.go:
@@ -21,7 +21,8 @@ type DBConfig struct {
}
type ApplicationConfig struct {
- Port int `json:"port" yaml:"port"`
+ Port int `json:"port" yaml:"port"`
+ PageSize int `json:"page_size" yaml:"page_size"`
}
type CacheConfig struct {
Setting a value for page_size
in config.yml:
@@ -1,5 +1,6 @@
app:
port: 8080
+ page_size: 5
db:
file_name: "test.db"
Wire in PageSize
in application/wire_helper.go:
@@ -15,7 +15,7 @@ type WireHelper struct {
}
func NewWireHelper(c *config.Config) (*WireHelper, error) {
- db, err := database.NewMySQLPersistence(c.DB.DSN)
+ db, err := database.NewMySQLPersistence(c.DB.DSN, c.App.PageSize)
if err != nil {
return nil, err
}
Update the query logic to include offset
and pageSize
in infrastructure/database/mysql.go:
@@ -13,10 +13,11 @@ import (
)
type MySQLPersistence struct {
- db *gorm.DB
+ db *gorm.DB
+ pageSize int
}
-func NewMySQLPersistence(dsn string) (*MySQLPersistence, error) {
+func NewMySQLPersistence(dsn string, pageSize int) (*MySQLPersistence, error) {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
@@ -24,7 +25,7 @@ func NewMySQLPersistence(dsn string) (*MySQLPersistence, error) {
// Auto Migrate the data structs
db.AutoMigrate(&model.Book{})
- return &MySQLPersistence{db}, nil
+ return &MySQLPersistence{db, pageSize}, nil
}
func (s *MySQLPersistence) CreateBook(ctx context.Context, b *model.Book) (uint, error) {
@@ -54,9 +55,9 @@ func (s *MySQLPersistence) GetBook(ctx context.Context, id uint) (*model.Book, e
return &book, nil
}
-func (s *MySQLPersistence) GetBooks(ctx context.Context) ([]*model.Book, error) {
+func (s *MySQLPersistence) GetBooks(ctx context.Context, offset int) ([]*model.Book, error) {
books := make([]*model.Book, 0)
- if err := s.db.WithContext(ctx).Find(&books).Error; err != nil {
+ if err := s.db.WithContext(ctx).Offset(offset).Limit(s.pageSize).Find(&books).Error; err != nil {
return nil, err
}
return books, nil
Tune cache keys in application/executor/book_operator.go:
@@ -3,6 +3,7 @@ package executor
import (
"context"
"encoding/json"
+ "fmt"
"literank.com/rest-books/domain/gateway"
"literank.com/rest-books/domain/model"
@@ -33,8 +34,9 @@ func (o *BookOperator) GetBook(ctx context.Context, id uint) (*model.Book, error
return o.bookManager.GetBook(ctx, id)
}
-func (o *BookOperator) GetBooks(ctx context.Context) ([]*model.Book, error) {
- rawValue, err := o.cacheHelper.Load(ctx, booksKey)
+func (o *BookOperator) GetBooks(ctx context.Context, offset int) ([]*model.Book, error) {
+ k := fmt.Sprintf("%s-%d", booksKey, offset)
+ rawValue, err := o.cacheHelper.Load(ctx, k)
if err != nil {
return nil, err
}
@@ -47,7 +49,7 @@ func (o *BookOperator) GetBooks(ctx context.Context) ([]*model.Book, error) {
}
} else {
// Cache key does not exist
- books, err = o.bookManager.GetBooks(ctx)
+ books, err = o.bookManager.GetBooks(ctx, offset)
if err != nil {
return nil, err
}
@@ -55,7 +57,7 @@ func (o *BookOperator) GetBooks(ctx context.Context) ([]*model.Book, error) {
if err != nil {
return nil, err
}
- if err := o.cacheHelper.Save(ctx, booksKey, string(value)); err != nil {
+ if err := o.cacheHelper.Save(ctx, k, string(value)); err != nil {
return nil, err
}
}
Last step, pass in the query parameters in adaptor/router.go:
@@ -12,7 +12,10 @@ import (
"literank.com/rest-books/domain/model"
)
-const fieldID = "id"
+const (
+ fieldID = "id"
+ fieldOffset = "o"
+)
// RestHandler handles all restful requests
type RestHandler struct {
@@ -50,7 +53,17 @@ func MakeRouter(wireHelper *application.WireHelper) (*gin.Engine, error) {
// Get all books
func (r *RestHandler) getBooks(c *gin.Context) {
- books, err := r.bookOperator.GetBooks(c)
+ offset := 0
+ offsetParam := c.Query(fieldOffset)
+ if offsetParam != "" {
+ value, err := strconv.Atoi(offsetParam)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid offset"})
+ return
+ }
+ offset = value
+ }
+ books, err := r.bookOperator.GetBooks(c, offset)
if err != nil {
fmt.Printf("Failed to get books: %v\n", err)
c.JSON(http.StatusNotFound, gin.H{"error": "failed to get books"})
Alright! Done with code changes. Let's try it out.
Try with curl
Put in some data for test
curl -X POST -H "Content-Type: application/json" -d '{"title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "published_at": "1925-04-10", "description": "A novel depicting the opulent lives of wealthy Long Island residents during the Jazz Age.", "isbn": "9780743273565", "total_pages": 218}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "To Kill a Mockingbird", "author": "Harper Lee", "published_at": "1960-07-11", "description": "A novel set in the American South during the 1930s, dealing with themes of racial injustice and moral growth.", "isbn": "9780061120084", "total_pages": 281}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "1984", "author": "George Orwell", "published_at": "1949-06-08", "description": "A dystopian novel depicting a totalitarian regime, surveillance, and propaganda.", "isbn": "9780451524935", "total_pages": 328}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "Pride and Prejudice", "author": "Jane Austen", "published_at": "1813-01-28", "description": "A classic novel exploring the themes of love, reputation, and social class in Georgian England.", "isbn": "9780486284736", "total_pages": 279}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "The Catcher in the Rye", "author": "J.D. Salinger", "published_at": "1951-07-16", "description": "A novel narrated by a disaffected teenager, exploring themes of alienation and identity.", "isbn": "9780316769488", "total_pages": 277}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "The Lord of the Rings", "author": "J.R.R. Tolkien", "published_at": "1954-07-29", "description": "A high fantasy epic following the quest to destroy the One Ring and defeat the Dark Lord Sauron.", "isbn": "9780544003415", "total_pages": 1178}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "Moby-Dick", "author": "Herman Melville", "published_at": "1851-10-18", "description": "A novel exploring themes of obsession, revenge, and the nature of good and evil.", "isbn": "9780142000083", "total_pages": 624}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "The Hobbit", "author": "J.R.R. Tolkien", "published_at": "1937-09-21", "description": "A fantasy novel set in Middle-earth, following the adventure of Bilbo Baggins and the quest for treasure.", "isbn": "9780345339683", "total_pages": 310}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "The Adventures of Huckleberry Finn", "author": "Mark Twain", "published_at": "1884-12-10", "description": "A novel depicting the journey of a young boy and an escaped slave along the Mississippi River.", "isbn": "9780486280615", "total_pages": 366}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "War and Peace", "author": "Leo Tolstoy", "published_at": "1869-01-01", "description": "A novel depicting the Napoleonic era in Russia, exploring themes of love, war, and historical determinism.", "isbn": "9781400079988", "total_pages": 1392}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "Alice’s Adventures in Wonderland", "author": "Lewis Carroll", "published_at": "1865-11-26", "description": "A children’s novel featuring a young girl named Alice who falls into a fantastical world populated by peculiar creatures.", "isbn": "9780141439761", "total_pages": 192}' http://localhost:8080/books
curl -X POST -H "Content-Type: application/json" -d '{"title": "The Odyssey", "author": "Homer", "published_at": "8th Century BC", "description": "An ancient Greek epic poem attributed to Homer, detailing the journey of Odysseus after the Trojan War.", "isbn": "9780140268867", "total_pages": 541}' http://localhost:8080/books
List books of first page
curl -X GET http://localhost:8080/books
Result:
[
{
"id": 1,
"title": "Great Book II",
"author": "Carl Smith",
"published_at": "2022-01-01 08:00:00.000",
"description": "Another sample book description",
"isbn": "8334567890",
"total_pages": 3880,
"created_at": "2024-02-25T16:29:31.353+08:00",
"updated_at": "2024-02-25T16:29:31.353+08:00"
},
{
"id": 2,
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"published_at": "1925-04-10",
"description": "A novel depicting the opulent lives of wealthy Long Island residents during the Jazz Age.",
"isbn": "9780743273565",
"total_pages": 218,
"created_at": "2024-02-26T17:28:26.542+08:00",
"updated_at": "2024-02-26T17:28:26.542+08:00"
},
{
"id": 3,
"title": "To Kill a Mockingbird",
"author": "Harper Lee",
"published_at": "1960-07-11",
"description": "A novel set in the American South during the 1930s, dealing with themes of racial injustice and moral growth.",
"isbn": "9780061120084",
"total_pages": 281,
"created_at": "2024-02-26T17:28:54.178+08:00",
"updated_at": "2024-02-26T17:28:54.178+08:00"
},
{
"id": 4,
"title": "1984",
"author": "George Orwell",
"published_at": "1949-06-08",
"description": "A dystopian novel depicting a totalitarian regime, surveillance, and propaganda.",
"isbn": "9780451524935",
"total_pages": 328,
"created_at": "2024-02-26T17:28:54.197+08:00",
"updated_at": "2024-02-26T17:28:54.197+08:00"
},
{
"id": 5,
"title": "Pride and Prejudice",
"author": "Jane Austen",
"published_at": "1813-01-28",
"description": "A classic novel exploring the themes of love, reputation, and social class in Georgian England.",
"isbn": "9780486284736",
"total_pages": 279,
"created_at": "2024-02-26T17:28:54.213+08:00",
"updated_at": "2024-02-26T17:28:54.213+08:00"
}
]
List books after an offset
curl -X GET "http://localhost:8080/books?o=5"
Result:
[
{
"id": 6,
"title": "The Catcher in the Rye",
"author": "J.D. Salinger",
"published_at": "1951-07-16",
"description": "A novel narrated by a disaffected teenager, exploring themes of alienation and identity.",
"isbn": "9780316769488",
"total_pages": 277,
"created_at": "2024-02-26T17:28:54.23+08:00",
"updated_at": "2024-02-26T17:28:54.23+08:00"
},
{
"id": 7,
"title": "The Lord of the Rings",
"author": "J.R.R. Tolkien",
"published_at": "1954-07-29",
"description": "A high fantasy epic following the quest to destroy the One Ring and defeat the Dark Lord Sauron.",
"isbn": "9780544003415",
"total_pages": 1178,
"created_at": "2024-02-26T17:28:54.246+08:00",
"updated_at": "2024-02-26T17:28:54.246+08:00"
},
{
"id": 8,
"title": "Moby-Dick",
"author": "Herman Melville",
"published_at": "1851-10-18",
"description": "A novel exploring themes of obsession, revenge, and the nature of good and evil.",
"isbn": "9780142000083",
"total_pages": 624,
"created_at": "2024-02-26T17:28:54.262+08:00",
"updated_at": "2024-02-26T17:28:54.262+08:00"
},
{
"id": 9,
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"published_at": "1937-09-21",
"description": "A fantasy novel set in Middle-earth, following the adventure of Bilbo Baggins and the quest for treasure.",
"isbn": "9780345339683",
"total_pages": 310,
"created_at": "2024-02-26T17:28:54.278+08:00",
"updated_at": "2024-02-26T17:28:54.278+08:00"
},
{
"id": 10,
"title": "The Adventures of Huckleberry Finn",
"author": "Mark Twain",
"published_at": "1884-12-10",
"description": "A novel depicting the journey of a young boy and an escaped slave along the Mississippi River.",
"isbn": "9780486280615",
"total_pages": 366,
"created_at": "2024-02-26T17:28:54.293+08:00",
"updated_at": "2024-02-26T17:28:54.293+08:00"
}
]
List books after another offset
curl -X GET "http://localhost:8080/books?o=10"
Result:
[
{
"id": 11,
"title": "War and Peace",
"author": "Leo Tolstoy",
"published_at": "1869-01-01",
"description": "A novel depicting the Napoleonic era in Russia, exploring themes of love, war, and historical determinism.",
"isbn": "9781400079988",
"total_pages": 1392,
"created_at": "2024-02-26T17:28:54.309+08:00",
"updated_at": "2024-02-26T17:28:54.309+08:00"
},
{
"id": 12,
"title": "The Odyssey",
"author": "Homer",
"published_at": "8th Century BC",
"description": "An ancient Greek epic poem attributed to Homer, detailing the journey of Odysseus after the Trojan War.",
"isbn": "9780140268867",
"total_pages": 541,
"created_at": "2024-02-26T17:29:22.386+08:00",
"updated_at": "2024-02-26T17:29:22.386+08:00"
},
{
"id": 13,
"title": "Alice’s Adventures in Wonderland",
"author": "Lewis Carroll",
"published_at": "1865-11-26",
"description": "A children’s novel featuring a young girl named Alice who falls into a fantastical world populated by peculiar creatures.",
"isbn": "9780141439761",
"total_pages": 192,
"created_at": "2024-02-26T17:31:55.903+08:00",
"updated_at": "2024-02-26T17:31:55.903+08:00"
}
]
Looks like pagination is working! So far so good!