Skip to content

Commit

Permalink
Add cache to CloudAPL (#312)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonatan Witoszek <[email protected]>
  • Loading branch information
lkostrowski and witoszekdev authored Dec 1, 2023
1 parent a3debcf commit 09b9185
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/four-carpets-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@saleor/app-sdk": minor
---

Saleor Cloud APL will now use built-in cache by default.
8 changes: 8 additions & 0 deletions src/APL/saleor-cloud/saleor-cloud-apl-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ export class SaleorCloudAplError extends Error {
this.name = "SaleorCloudAplError";
}
}

export const CloudAplError = {
FAILED_TO_REACH_API: "FAILED_TO_REACH_API",
RESPONSE_BODY_INVALID: "RESPONSE_BODY_INVALID",
RESPONSE_NON_200: "RESPONSE_NON_200",
ERROR_SAVING_DATA: "ERROR_SAVING_DATA",
ERROR_DELETING_DATA: "ERROR_DELETING_DATA",
};
31 changes: 31 additions & 0 deletions src/APL/saleor-cloud/saleor-cloud-apl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,37 @@ describe("APL", () => {

expect(await apl.get("http://unknown-domain.example.com/graphql/")).toBe(undefined);
});

it("Uses cache when GET call is called 2nd time", async () => {
fetchMock.mockResolvedValue({
status: 200,
ok: true,
json: async () => ({
saleor_app_id: stubAuthData.appId,
saleor_api_url: stubAuthData.saleorApiUrl,
jwks: stubAuthData.jwks,
domain: stubAuthData.domain,
token: stubAuthData.token,
}),
});

const apl = new SaleorCloudAPL(aplConfig);

expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData);
expect(await apl.get(stubAuthData.saleorApiUrl)).toStrictEqual(stubAuthData);

expect(fetchMock).toBeCalledTimes(1);
expect(fetchMock).toBeCalledWith(
"https://example.com/aHR0cHM6Ly9leGFtcGxlLmNvbS9ncmFwaHFsLw", // base64 encoded api url
{
headers: {
"Content-Type": "application/json",
Authorization: "Bearer token",
},
method: "GET",
}
);
});
});
});

Expand Down
27 changes: 18 additions & 9 deletions src/APL/saleor-cloud/saleor-cloud-apl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { getOtelTracer, OTEL_APL_SERVICE_NAME } from "../../open-telemetry";
import { APL, AplConfiguredResult, AplReadyResult, AuthData } from "../apl";
import { createAPLDebug } from "../apl-debug";
import { authDataFromObject } from "../auth-data-from-object";
import { SaleorCloudAplError } from "./saleor-cloud-apl-errors";
import { CloudAplError, SaleorCloudAplError } from "./saleor-cloud-apl-errors";

const debug = createAPLDebug("SaleorCloudAPL");

export type SaleorCloudAPLConfig = {
resourceUrl: string;
token: string;
cacheManager?: Map<string, AuthData>;
};

type CloudAPLAuthDataShape = {
Expand All @@ -28,14 +29,6 @@ export type GetAllAplResponseShape = {
results: CloudAPLAuthDataShape[];
};

export const CloudAplError = {
FAILED_TO_REACH_API: "FAILED_TO_REACH_API",
RESPONSE_BODY_INVALID: "RESPONSE_BODY_INVALID",
RESPONSE_NON_200: "RESPONSE_NON_200",
ERROR_SAVING_DATA: "ERROR_SAVING_DATA",
ERROR_DELETING_DATA: "ERROR_DELETING_DATA",
};

const validateResponseStatus = (response: Response) => {
if (!response.ok) {
debug("Response failed with status %s", response.status);
Expand Down Expand Up @@ -92,13 +85,16 @@ export class SaleorCloudAPL implements APL {

private tracer: Tracer;

private cacheManager: Map<string, AuthData>;

constructor(config: SaleorCloudAPLConfig) {
this.resourceUrl = config.resourceUrl;
this.headers = {
Authorization: `Bearer ${config.token}`,
};

this.tracer = getOtelTracer();
this.cacheManager = config.cacheManager ?? new Map<string, AuthData>();
}

private getUrlForDomain(saleorApiUrl: string) {
Expand All @@ -107,6 +103,13 @@ export class SaleorCloudAPL implements APL {
}

async get(saleorApiUrl: string): Promise<AuthData | undefined> {
const cachedData = this.cacheManager.get(saleorApiUrl);

if (cachedData) {
debug("Returning authData from cache for saleorApiUrl %s", saleorApiUrl)
return cachedData;
}

debug("Will fetch data from SaleorCloudAPL for saleorApiUrl %s", saleorApiUrl);

return this.tracer.startActiveSpan(
Expand Down Expand Up @@ -224,6 +227,8 @@ export class SaleorCloudAPL implements APL {

span.setAttribute("appId", authData.appId);

this.cacheManager.set(saleorApiUrl, authData);

span.end();

return authData;
Expand Down Expand Up @@ -270,6 +275,8 @@ export class SaleorCloudAPL implements APL {

debug("Set command finished successfully for saleorApiUrl: %", authData.saleorApiUrl);

this.cacheManager.set(authData.saleorApiUrl, authData);

span.setStatus({
code: SpanStatusCode.OK,
});
Expand All @@ -289,6 +296,8 @@ export class SaleorCloudAPL implements APL {
headers: { "Content-Type": "application/json", ...this.headers },
});

this.cacheManager.delete(saleorApiUrl);

debug(`Delete responded with ${response.status} code`);
} catch (error) {
const errorMessage = extractErrorMessage(error);
Expand Down

0 comments on commit 09b9185

Please sign in to comment.