Skip to content

Commit 6f0220f

Browse files
committed
feat: App multi-tenancy support in single ServerCore instance
1 parent eb72e95 commit 6f0220f

File tree

9 files changed

+327
-217
lines changed

9 files changed

+327
-217
lines changed

packages/cubejs-api-gateway/index.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ class ApiGateway {
221221
});
222222
const normalizedQuery = normalizeQuery(query);
223223
const [compilerSqlResult, metaConfigResult] = await Promise.all([
224-
this.compilerApi.getSql(coerceForSqlQuery(normalizedQuery, req)),
225-
this.compilerApi.metaConfig()
224+
this.getCompilerApi(req).getSql(coerceForSqlQuery(normalizedQuery, req)),
225+
this.getCompilerApi(req).metaConfig()
226226
]);
227227
const sqlQuery = compilerSqlResult;
228228
const metaConfig = metaConfigResult;
@@ -231,7 +231,7 @@ class ApiGateway {
231231
const toExecute = {
232232
...sqlQuery, query: sqlQuery.sql[0], values: sqlQuery.sql[1], continueWait: true
233233
};
234-
const response = await this.adapterApi.executeQuery(toExecute);
234+
const response = await this.getAdapterApi(req).executeQuery(toExecute);
235235
this.log(req, {
236236
type: 'Load Request Success',
237237
query: req.query.query,
@@ -255,7 +255,7 @@ class ApiGateway {
255255
try {
256256
const query = JSON.parse(req.query.query);
257257
const normalizedQuery = normalizeQuery(query);
258-
const sqlQuery = await this.compilerApi.getSql(coerceForSqlQuery(normalizedQuery, req));
258+
const sqlQuery = await this.getCompilerApi(req).getSql(coerceForSqlQuery(normalizedQuery, req));
259259
res.json({
260260
sql: sqlQuery
261261
});
@@ -266,7 +266,7 @@ class ApiGateway {
266266

267267
app.get(`${this.basePath}/v1/meta`, this.checkAuthMiddleware, (async (req, res) => {
268268
try {
269-
const metaConfig = await this.compilerApi.metaConfig();
269+
const metaConfig = await this.getCompilerApi(req).metaConfig();
270270
const cubes = metaConfig.map(c => c.config);
271271
res.json({ cubes });
272272
} catch (e) {
@@ -275,6 +275,24 @@ class ApiGateway {
275275
}));
276276
}
277277

278+
getCompilerApi(req) {
279+
if (typeof this.compilerApi === 'function') {
280+
return this.compilerApi(this.contextByReq(req));
281+
}
282+
return this.compilerApi;
283+
}
284+
285+
getAdapterApi(req) {
286+
if (typeof this.adapterApi === 'function') {
287+
return this.adapterApi(this.contextByReq(req));
288+
}
289+
return this.adapterApi;
290+
}
291+
292+
contextByReq(req) {
293+
return { authInfo: req.authInfo };
294+
}
295+
278296
handleError(e, req, res) {
279297
if (e instanceof UserError) {
280298
this.log(req, {
Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,45 @@
1+
const TerserPlugin = require('terser-webpack-plugin');
2+
const webpack = require('webpack');
3+
14
module.exports = function override(config, env) {
2-
config.optimization = { minimize: false };
5+
config.optimization = {
6+
minimizer: [
7+
new TerserPlugin({
8+
cache: true,
9+
parallel: true,
10+
chunkFilter: (chunk) => chunk.name.indexOf('babel') === -1 // && chunk.name.indexOf('vendors') === -1
11+
})
12+
],
13+
splitChunks: {
14+
chunks: 'all',
15+
minSize: 30000,
16+
maxSize: 0,
17+
minChunks: 1,
18+
maxAsyncRequests: 5,
19+
maxInitialRequests: 3,
20+
automaticNameDelimiter: '~',
21+
name: true,
22+
cacheGroups: {
23+
babel: {
24+
test: /babel/,
25+
priority: -5
26+
},
27+
vendors: {
28+
test: /[\\/]node_modules[\\/]/,
29+
priority: -10
30+
},
31+
default: {
32+
priority: -20
33+
}
34+
}
35+
}
36+
};
37+
config.stats = 'verbose';
38+
config.plugins = config.plugins.concat([
39+
new webpack.ProgressPlugin()
40+
]);
41+
if (env === 'production') {
42+
config.devtool = false;
43+
}
344
return config;
445
};

packages/cubejs-playground/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
},
3535
"scripts": {
3636
"start": "react-app-rewired start",
37-
"build": "react-app-rewired build && cp -R build/ ../cubejs-server-core/playground/",
37+
"build": "react-app-rewired build && rm -Rf ../cubejs-server-core/playground/** && cp -R build/ ../cubejs-server-core/playground/",
3838
"test": "react-app-rewired test",
3939
"eject": "react-scripts eject"
4040
},
@@ -59,6 +59,7 @@
5959
"eslint-plugin-import": "^2.16.0",
6060
"eslint-plugin-jsx-a11y": "^6.2.1",
6161
"eslint-plugin-react": "^7.12.4",
62-
"react-app-rewired": "^2.1.0"
62+
"react-app-rewired": "^2.1.0",
63+
"terser-webpack-plugin": "^1.2.3"
6364
}
6465
}

packages/cubejs-playground/yarn.lock

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,10 +1063,10 @@
10631063
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
10641064
integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==
10651065

1066-
"@cubejs-client/core@^0.4.5":
1067-
version "0.4.5"
1068-
resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.4.5.tgz#6df4b6c15fbf678323d14bbaae83607ca1ebe437"
1069-
integrity sha512-EGitKr6zPtsOcMyFSDe6fLKS7nzFhMIptnbMgEPbvfhlqw4CyvSACSBoThMC4Sa3NVOjpmGOTnzKrglzegE3sw==
1066+
"@cubejs-client/core@^0.6.0":
1067+
version "0.6.0"
1068+
resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.6.0.tgz#86b36526936a569e9de0b61bab4aecf9f9bb7584"
1069+
integrity sha512-z23Hghs6QH5E1/8nNAzqjWRtlmyqIsIFM8oSjj+/5npjwVl63RgHILex+nNzlTCanYZrHtkC+Fnta4ThmG9O5g==
10701070
dependencies:
10711071
"@babel/runtime" "^7.1.2"
10721072
core-js "^2.5.3"
@@ -1075,10 +1075,10 @@
10751075
ramda "^0.25.0"
10761076
whatwg-fetch "^3.0.0"
10771077

1078-
"@cubejs-client/react@^0.4.5":
1079-
version "0.4.5"
1080-
resolved "https://registry.yarnpkg.com/@cubejs-client/react/-/react-0.4.5.tgz#4cc4dbf86865a53a37d63f8ee09ff1f5653cf078"
1081-
integrity sha512-hryecjoLOqFs1fEHtu0ErZZidbDGQIw6CJiUVXzQbqt7jhh7/GaD3jVcbshm0UDfH9sAPDKoYT0+jZv54o2lbA==
1078+
"@cubejs-client/react@^0.6.0":
1079+
version "0.6.0"
1080+
resolved "https://registry.yarnpkg.com/@cubejs-client/react/-/react-0.6.0.tgz#31516d96467a3d09fb018fb00b9f619cb465b7e1"
1081+
integrity sha512-9xwVp7ankCrF4wVdEbnYLNYNHMInavJCGnKObKhpzpAV0gqD3sGZHPAzmS2bf+WzxF9PWDkIJiooUCiTlJT9pQ==
10821082
dependencies:
10831083
"@babel/runtime" "^7.1.2"
10841084
prop-types "^15.6.2"
@@ -10816,6 +10816,20 @@ terser-webpack-plugin@1.2.2, terser-webpack-plugin@^1.1.0:
1081610816
webpack-sources "^1.1.0"
1081710817
worker-farm "^1.5.2"
1081810818

10819+
terser-webpack-plugin@^1.2.3:
10820+
version "1.2.3"
10821+
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8"
10822+
integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==
10823+
dependencies:
10824+
cacache "^11.0.2"
10825+
find-cache-dir "^2.0.0"
10826+
schema-utils "^1.0.0"
10827+
serialize-javascript "^1.4.0"
10828+
source-map "^0.6.1"
10829+
terser "^3.16.1"
10830+
webpack-sources "^1.1.0"
10831+
worker-farm "^1.5.2"
10832+
1081910833
terser@^3.16.1:
1082010834
version "3.16.1"
1082110835
resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493"

packages/cubejs-server-core/core/index.js

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ class CubejsServerCore {
6666
}
6767
});
6868
this.repository = new FileRepository(this.schemaPath);
69+
this.repositoryFactory = options.repositoryFactory || (() => this.repository);
70+
this.contextToDbType = typeof options.dbType === 'function' ? options.dbType : () => options.dbType;
71+
this.appIdToCompilerApi = {};
72+
this.appIdToOrchestratorApi = {};
73+
this.contextToAppId = options.contextToAppId || (() => process.env.CUBEJS_APP || 'STANDALONE');
74+
this.orchestratorOptions =
75+
typeof options.orchestratorOptions === 'function' ?
76+
options.orchestratorOptions :
77+
() => options.orchestratorOptions;
6978

7079
const Analytics = require('analytics-node');
7180
const client = new Analytics('dSR8JiNYIGKyQHKid9OaLYugXLao18hA', { flushInterval: 100 });
@@ -148,12 +157,10 @@ class CubejsServerCore {
148157

149158
async initApp(app) {
150159
checkEnvForPlaceholders();
151-
this.compilerApi = this.createCompilerApi(this.repository);
152-
this.orchestratorApi = this.createOrchestratorApi();
153160
const apiGateway = new ApiGateway(
154161
this.apiSecret,
155-
this.compilerApi,
156-
this.orchestratorApi,
162+
this.getCompilerApi.bind(this),
163+
this.getOrchestratorApi.bind(this),
157164
this.logger, {
158165
basePath: this.options.basePath,
159166
checkAuthMiddleware: this.options.checkAuthMiddleware
@@ -165,18 +172,55 @@ class CubejsServerCore {
165172
}
166173
}
167174

168-
createCompilerApi(repository) {
169-
return new CompilerApi(repository, this.dbType, {
170-
schemaVersion: this.options.schemaVersion,
175+
getCompilerApi(context) {
176+
const appId = this.contextToAppId(context);
177+
if (!this.appIdToCompilerApi[appId]) {
178+
this.appIdToCompilerApi[appId] = this.createCompilerApi(
179+
this.repositoryFactory(context), {
180+
dbType: this.contextToDbType(context),
181+
schemaVersion: this.options.schemaVersion && (() => this.options.schemaVersion(context))
182+
}
183+
);
184+
}
185+
return this.appIdToCompilerApi[appId];
186+
}
187+
188+
getOrchestratorApi(context) {
189+
const appId = this.contextToAppId(context);
190+
if (!this.appIdToOrchestratorApi[appId]) {
191+
let driverPromise;
192+
this.appIdToOrchestratorApi[appId] = this.createOrchestratorApi({
193+
getDriver: () => {
194+
if (!driverPromise) {
195+
const driver = this.driverFactory(context);
196+
driverPromise = driver.testConnection().then(() => driver).catch(e => {
197+
driverPromise = null;
198+
throw e;
199+
});
200+
}
201+
return driverPromise;
202+
},
203+
redisPrefix: appId,
204+
orchestratorOptions: this.orchestratorOptions(context)
205+
});
206+
}
207+
return this.appIdToOrchestratorApi[appId];
208+
}
209+
210+
createCompilerApi(repository, options) {
211+
options = options || {};
212+
return new CompilerApi(repository, options.dbType || this.dbType, {
213+
schemaVersion: options.schemaVersion || this.options.schemaVersion,
171214
devServer: this.options.devServer,
172215
logger: this.logger
173216
});
174217
}
175218

176-
createOrchestratorApi() {
177-
return new OrchestratorApi(() => this.getDriver(), this.logger, {
178-
redisPrefix: process.env.CUBEJS_APP,
179-
...this.options.orchestratorOptions
219+
createOrchestratorApi(options) {
220+
options = options || {};
221+
return new OrchestratorApi(options.getDriver || this.getDriver.bind(this), this.logger, {
222+
redisPrefix: options.redisPrefix || process.env.CUBEJS_APP,
223+
...(options.orchestratorOptions || this.options.orchestratorOptions)
180224
});
181225
}
182226

0 commit comments

Comments
 (0)