Skip to content

Commit 35bd92f

Browse files
authored
feat: add redis cache support (#26)
* feat: add redis cache support * feat: update tests and yaml
1 parent 5ae1f50 commit 35bd92f

File tree

15 files changed

+113
-22
lines changed

15 files changed

+113
-22
lines changed

.github/workflows/test.yml

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ jobs:
4646
strategy:
4747
matrix:
4848
app: ["transaction_service"]
49+
services:
50+
redis:
51+
image: redis:alpine
52+
ports:
53+
- 6379:6379
4954
steps:
5055
- uses: actions/checkout@v3
5156
- uses: swift-actions/setup-swift@v1
@@ -55,6 +60,7 @@ jobs:
5560
env:
5661
DATABASE_URL: ${{ secrets.DATABASE_URL }}
5762
RPC_URL: ${{ secrets.RPC_URL }}
63+
REDIS_URL: redis://localhost:6379
5864

5965
docker-service:
6066
name: Build dockerized services

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ services/**/bin
4242
.idea
4343
.build
4444
__pycache__
45+
46+
data

docker-compose.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ services:
1313
- "8080:8080"
1414
environment:
1515
- RPC_URL=${RPC_URL}
16-
- DATABASE_URL=${DATABASE_URL}
16+
- DATABASE_URL=${DATABASE_URL}
17+
redis:
18+
restart: always
19+
container_name: redis
20+
image: redis:alpine
21+
ports:
22+
- 6379:6379
23+
volumes:
24+
- ./data/redis-data:/data

services/transaction_service/Package.resolved

+18
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,24 @@
9999
"version" : "2.0.7"
100100
}
101101
},
102+
{
103+
"identity" : "redis",
104+
"kind" : "remoteSourceControl",
105+
"location" : "https://github.com/vapor/redis.git",
106+
"state" : {
107+
"revision" : "e955843b08064071f465a6b1ca9e04bebad8623a",
108+
"version" : "4.6.0"
109+
}
110+
},
111+
{
112+
"identity" : "redistack",
113+
"kind" : "remoteSourceControl",
114+
"location" : "https://gitlab.com/mordil/RediStack.git",
115+
"state" : {
116+
"revision" : "5458d6476e05d5f1b43097f1bc9b599e936b5f2f",
117+
"version" : "1.3.0"
118+
}
119+
},
102120
{
103121
"identity" : "routing-kit",
104122
"kind" : "remoteSourceControl",

services/transaction_service/Package.swift

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ let package = Package(
1212
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
1313
.package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.0.0"),
1414
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"),
15+
.package(url: "https://github.com/vapor/redis.git", from: "4.0.0"),
1516
.package(path: "../../swift_packages/common"),
1617
.package(path: "../../swift_packages/model"),
1718
.package(path: "../../swift_packages/env"),
@@ -23,6 +24,7 @@ let package = Package(
2324
.product(name: "Fluent", package: "fluent"),
2425
.product(name: "FluentMongoDriver", package: "fluent-mongo-driver"),
2526
.product(name: "Vapor", package: "vapor"),
27+
.product(name: "Redis", package: "redis"),
2628
.product(name: "Alamofire", package: "Alamofire"),
2729
.product(name: "common", package: "common"),
2830
.product(name: "model", package: "model"),

services/transaction_service/Sources/App/Constants/Environments.swift

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ import Foundation
99

1010
let ENVIRONMENT_DB_KEY = "DATABASE_URL"
1111
let ENVIRONMENT_RPC_URL_KEY = "RPC_URL"
12+
let ENVIRONMENT_REDIS_KEY = "REDIS_URL"

services/transaction_service/Sources/App/Controllers/transaction/Transaction+id.swift

+8-6
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import Vapor
1010
import Alamofire
1111
import model
1212
import common
13+
import Redis
1314

1415
typealias Request = Vapor.Request
1516

1617
extension TransactionController {
1718
/**
1819
Get user data by user
1920
*/
20-
func getUser(id: HexString, page: Int?, per: Int?, db: Database) async -> User? {
21+
func getUser(id: HexString, page: Int?, per: Int?, db: DatabaseClient) async -> User? {
2122
let task = AF.request(Environment.get(ENVIRONMENT_RPC_URL_KEY)!,
2223
method: .post,
2324
parameters: BalanceRequest(params: [id.stringValue ?? "", "latest"]),
@@ -29,7 +30,7 @@ extension TransactionController {
2930
}
3031

3132

32-
let result = try? await Transaction.query(on: db)
33+
let result = try? await Transaction.query(on: db.databaseClient)
3334
.group(.or) {
3435
group in
3536
group.filter(\.$from == id).filter(\.$to == id)
@@ -46,8 +47,8 @@ extension TransactionController {
4647
/**
4748
Get Block by block id
4849
*/
49-
func getBlock(id: HexString, with db: Database) async -> Block? {
50-
let result = try? await Block.query(on: db)
50+
func getBlock(id: HexString, with db: DatabaseClient) async -> Block? {
51+
let result = try? await Block.query(on: db.databaseClient)
5152
.filter(\.$hash == id)
5253
.first()
5354
return result
@@ -56,7 +57,7 @@ extension TransactionController {
5657
/**
5758
Find transaction by transaction id
5859
*/
59-
func getTransaction(id: HexString) async -> Transaction? {
60+
func getTransaction(id: HexString, with database: DatabaseClient) async -> Transaction? {
6061
let task = AF.request(Environment.get("RPC_URL")!,
6162
method: .post,
6263
parameters: TransactionRequest(params: [id]),
@@ -74,8 +75,9 @@ extension TransactionController {
7475

7576

7677
let hexStringID = try HexString(id)
78+
let dbClient = DatabaseClient(logger: req.logger, cacheClient: req.redis, databaseClient: req.db)
7779

78-
let result = try await findById(id: hexStringID, with: req.db, page: query.page, perPage: query.per) as? QueryResponse
80+
let result = try await findById(id: hexStringID, with: dbClient, page: query.page, perPage: query.per) as? QueryResponse
7981
if let result = result {
8082
return result
8183
}

services/transaction_service/Sources/App/Controllers/transaction/TransactionController.swift

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Fluent
22
import Vapor
33
import Alamofire
4+
import Redis
45
import model
56
import common
67

@@ -11,6 +12,7 @@ struct TransactionQuery: Content {
1112
}
1213

1314
struct TransactionController: RouteCollection, TransactionProtocol {
15+
1416
func boot(routes: RoutesBuilder) throws {
1517
routes.get("stats", "transaction", ":id", use: self.get)
1618
routes.get("stats", "transaction", "health", use: self.getHealth)
@@ -26,16 +28,23 @@ struct TransactionController: RouteCollection, TransactionProtocol {
2628
/**
2729
Get transaction, block, or user info by id
2830
*/
29-
func findById(id: HexString, with database: Database?, page: Int?, perPage: Int?) async throws -> QueryResponseProtocol {
30-
guard let database = database else {
31-
throw InvalidTypeError.invalidRequestType
31+
func findById(id: HexString, with database: DatabaseClient, page: Int?, perPage: Int?) async throws -> QueryResponseProtocol {
32+
let redisKey = RedisKey("\(id.stringValue!)?start=\(page ?? 0)&end=\(perPage ?? 0)")
33+
let cached = try await database.cacheClient.get(redisKey, asJSON: QueryResponse.self)
34+
if let cached = cached {
35+
database.logger.info("Found cache for key \(redisKey)")
36+
return cached
3237
}
33-
34-
return try await QueryResponse.fromData(
38+
let result = try await QueryResponse.fromData(
3539
id: id,
36-
transactionData: getTransaction(id: id),
40+
transactionData: getTransaction(id: id, with: database),
3741
blockData: getBlock(id: id, with: database),
3842
userData: getUser(id: id, page: page, per: perPage, db: database)
3943
)
44+
// save cache
45+
if result.shouldCache {
46+
try? await database.cacheClient.set(redisKey, toJSON: result)
47+
}
48+
return result
4049
}
4150
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// Created by Qiwei Li on 6/22/22.
3+
//
4+
5+
import Foundation
6+
import common
7+
import Vapor
8+
import Fluent
9+
10+
struct DatabaseClient: DatabaseProtocol {
11+
var logger: Logger
12+
var cacheClient: Request.Redis
13+
var databaseClient: Database
14+
}

services/transaction_service/Sources/App/Types/Response.swift

+15-5
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import model
1414
struct QueryResponse: Content, QueryResponseProtocol{
1515
var type: DataType
1616
var data: Any
17+
var shouldCache: Bool
1718

1819
private enum CodingKeys: String, CodingKey { case type, data }
1920

2021

2122
init(from decoder: Decoder) throws {
2223
let container = try decoder.container(keyedBy: CodingKeys.self)
2324
type = try container.decode(DataType.self, forKey: .type)
25+
shouldCache = false
2426

2527
if let data = try? container.decode(Transaction.self, forKey: .data) {
2628
self.data = data
@@ -36,13 +38,13 @@ struct QueryResponse: Content, QueryResponseProtocol{
3638
self.data = data
3739
return
3840
}
39-
4041
throw InvalidTypeError.invalidRequestType
4142
}
4243

43-
init(type: DataType, data: Any){
44+
init(type: DataType, data: Any, shouldCache: Bool){
4445
self.type = type
4546
self.data = data
47+
self.shouldCache = shouldCache
4648
}
4749

4850
func encode(to encoder: Encoder) throws {
@@ -65,16 +67,24 @@ struct QueryResponse: Content, QueryResponseProtocol{
6567

6668
extension QueryResponse {
6769
static func fromData(id: HexString, transactionData: Transaction?, blockData: Block?, userData: User?) throws -> QueryResponse{
70+
var shouldCache: Bool = false
71+
6872
if let transactionData = transactionData {
69-
return QueryResponse(type: .transaction, data: transactionData)
73+
if let _ = transactionData.blockNumber {
74+
// unconfirmed transaction
75+
shouldCache = true
76+
}
77+
return QueryResponse(type: .transaction, data: transactionData,shouldCache: shouldCache)
7078
}
7179

7280
if let blockData = blockData {
73-
return QueryResponse(type: .block, data: blockData)
81+
shouldCache = true
82+
return QueryResponse(type: .block, data: blockData,shouldCache: shouldCache)
7483
}
7584

7685
if let userData = userData {
77-
return QueryResponse(type: .user, data: userData)
86+
shouldCache = true
87+
return QueryResponse(type: .user, data: userData, shouldCache: shouldCache)
7888
}
7989

8090
throw InvalidHashError.hashNotFound(id: id)

services/transaction_service/Sources/App/configure.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import FluentMongoDriver
33
import Vapor
44
import common
55
import env
6+
import Redis
67

78
// configures your application
89
public func configure(_ app: Application) throws {
910
// uncomment to serve files from /Public folder
1011
// app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
11-
let checker = EnvChecker(envs: [ENVIRONMENT_DB_KEY, ENVIRONMENT_RPC_URL_KEY])
12+
let checker = EnvChecker(envs: [ENVIRONMENT_DB_KEY, ENVIRONMENT_RPC_URL_KEY, ENVIRONMENT_REDIS_KEY])
1213
let checkResult = checker.check()
1314

1415
if !checkResult.isNotMissing {
@@ -21,6 +22,8 @@ public func configure(_ app: Application) throws {
2122
connectionString: Environment.get(ENVIRONMENT_DB_KEY)!
2223
), as: .mongo)
2324

25+
// config redis
26+
app.redis.configuration = try RedisConfiguration(url: URL(string: Environment.get(ENVIRONMENT_REDIS_KEY)!)!)
2427

2528
// register routes
2629
try routes(app)

services/transaction_service/Tests/AppTests/AppTests.swift

+5
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,10 @@ final class AppTests: XCTestCase {
1010
try app.test(.GET, "stats/transaction/health", afterResponse: { res in
1111
XCTAssertEqual(res.status, .ok)
1212
})
13+
14+
// test get transaction by id
15+
try app.test(.GET, "stats/transaction/0xcf90ba1824dc2b58c6631c72dbedd54e155f205602613f623c026e957acb45b0", afterResponse: { res in
16+
XCTAssertEqual(res.status, .ok)
17+
})
1318
}
1419
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//
2+
// Created by Qiwei Li on 6/22/22.
3+
//
4+
5+
import Foundation
6+
7+
public protocol DatabaseProtocol {
8+
9+
}

swift_packages/common/Sources/common/Protocols/TransactionProtocol.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
import Foundation
99
import Fluent
10+
import Vapor
1011

1112
public protocol TransactionProtocol: HealthProtocol {
12-
func findById(id: HexString, with database: Database?, page: Int?, perPage: Int?) async throws -> QueryResponseProtocol
13+
associatedtype DBClient: DatabaseProtocol
14+
func findById(id: HexString, with database: DBClient, page: Int?, perPage: Int?) async throws -> QueryResponseProtocol
1315
}

swift_packages/model/Sources/model/Transaction.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ public final class Transaction: Model, Content {
1717
public var nonce: HexString
1818

1919
@Field(key: "blockHash")
20-
public var blockHash: HexString
20+
public var blockHash: HexString?
2121

2222
@Field(key: "blockNumber")
23-
public var blockNumber: HexString
23+
public var blockNumber: HexString?
2424

2525
@Field(key: "transactionIndex")
2626
public var transactionIndex: HexString

0 commit comments

Comments
 (0)