Big Apps:
Mono-app,
Mono-repo,
Mico-UI; oh my!

L&L March 2022
Steve Olsen

GOALS:
- To learn something new
- Smile twice (or more)
Ways to shave
Pros and cons
- No "one right" answer!
- Tradeoffs abound
- Implementation details mater, too!
# The problem
What's all the ado?

Sharing code is hard

- Dependencies * (intra-product)
- Code reuse
- Feature vs. "Bug"
- Breaking changes
- Versioning
- Hosting
- Permissions
Modules (internal)
vs.
Libraries (external)
Dependencies
Mono-app
Mono-repo
Micro-UI
Module
Library
Goals:
-
Higher dev productivity
-
Easier code reuse/sharing (where it makes sense)
-
Less code breakage
iframe





Tell Me More.
# whynotlibs
i.e. "highly-decoupled code"
Theory vs. Practical
- In theory, they:
- are separate/decouple concerns
- are highly specialized
- work well
- In practice, they:
- break dependants easily
- get mis-versioned
- become low-visibility, "high-wall"
- go stale/ "broken window" syndrome
- mask teams' communication problems
For an example experience, see:
https://www.youtube.com/watch?v=0_qhdOeMuhk
Fit and purpose
Libs are best used for generic, highly-reusable, non-business related data problems.
Eg. formatters for dates/numbers, animations, etc.
*General tools, where any dependants don't affect them.*
# UX 101
Product Pipeline!
- "We want omnisearch in SuperApp"
- "We want SnapMoney widgets in SuperApp"

PR: link
Engineering Response:

- see "boxes"
- identify "patterns"
Modules!

"so many modules!"
Mobile

Desktop

Parts
*widget: smart components
*libs: pure functions/code
*layouts: adapt to their container size

# Namespacing and Refactoring 101
Sharing is caring!

Namespacing via file path
- /branch-a/
- /branch-b/
- /leaf/
- index.ts
branchA.branchB.leaf
import @/branch-a/branch-b/leaf
VERY important how scoping is defined...

Page-scoped component
/src/
/ui/
/pages/
/checkout/
index.ts
Page.ts << imports ./select-box-auto
/select-box-auto/...
Common-scoped component
/src/
/ui/
/common/
/components/
/select-box-auto/...
/pages/
/checkout/
index.ts
Page.ts << imports ../../common/components/select-box-auto
/sign-up/
index.ts
Page.ts << imports ../../common/components/select-box-auto

Page-scoped widget
/src/
/ui/
/pages/
/checkout/
index.ts
Page.ts << imports ./cart
/cart/...
Common-scoped widget
/src/
/ui/
/common/
/widgets/
/cart/...
/pages/
/checkout/
index.ts
Page.ts << imports ../../common/widgets/cart
/orders/
index.ts
Page.ts << imports ../../common/widgets/cart

Specificity
- /parent-a/
- /parent-b/
- /child/
- index.ts
generalize
specialize
# Monos
all your code is share to us

// Assuming NextJS
package.json
/pages/... <<< routing {URL:Page} and {URL:API}
/src/
/api/... <<< define HTTP API handlers
/common/ <<< eg. libs, frmt, types, validators, etc.
/libs/
/ui/
/common/... <<< Atlas, etc.
/widgets/
/components/
/libs/
/[vertical]/
/pages/...
/widgets/...
/components/...
Mono-apps
folder structure
// Assuming yarn workspaces
package.json
/apps/ <<< self bundled / deployable units
/web-uni/...
/web-money/...
/web-travel/...
/web-shop/...
/packages/ <<< sharable units
/@web-common/... <<< eg. libs, types, validators, etc.
/@web-[vertical]/
/pages/...
/widgets/...
/components/...
Mono-repos
Each /apps/* deploys its own bundle
folder structure
Eg. Assemble web-uni!
packages/@web-uni/nav
packages/@web-uni/nav
app/web-uni/pages
packages/
@web-snapmoney/ components/
cta-activate/...
packages/
@web-snaptravel/ widgets/
bookings/...

packages/
@web-snapshop/ widgets/
daily-deals/...
packages/
@web-common/ components/
cta-ad/...
# Micro-UI

<html>
<head>
<script src="./app/main.js"></script>
<script src="./app/nav.js"></script>
<script src="./app/travel.js"></script>
<script src="./app/shop.js"></script>
<script src="./app/money.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>Snap it Together
verticals = travel, shop, money, .....
import React, { Suspense, lazy } from "react";
...
const MoneyPage = lazy(() => import("./pkg-money/Home"));
...
<Suspense fallback={<div>Loading...<div>}>
<Switch>
<Route exact={true} path="/money" component={MoneyPage} />Suspense + lazy
ES "async loading" other packages
Composition Example

Module Federation
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const mode = process.env.NODE_ENV || 'production';
module.exports = {
mode,
entry: './src/index',
devtool: 'source-map',
optimization: {
minimize: mode === 'production',
},
resolve: {
extensions: ['.jsx', '.js', '.json'],
},
module: {
rules: [ /* ... */ ],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new ModuleFederationPlugin({
name: 'remote', // this name needs to match with the entry name
// ...
}),
],
};// Assuming yarn workspaces
package.json
/packages/
/@pkg-main-app/
/@pkg-common/ <<< eg. libs, types, validators, etc.
/@pkg-[vertical]/
/pages/
/widgets/
/components/
Mono-repo for a Micro-UI!
Each package is self-hosted and deployed
Folder structure
Multi-repos for a Micro-UI!
Note: Libs/Components:
- have low coupling/ no co-dependencies
- are self-hosted and deployed
- are imported/referenced by other pkgs
<html>
<head>
<script src="./app/main.js"></script>
<script src="./app/nav.js"></script>
<script src="./app/travel.js"></script>
<script src="./app/shop.js"></script>
<script src="./app/money.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>Bonus Dev: Local vs. proxy to staging
localhost
localhost
stagging
Resources
- https://blog.bredvid.no/micro-frontends-with-module-federation-e4ed75fcc328
- https://levelup.gitconnected.com/micro-frontends-step-by-step-using-react-webpack-5-and-module-federation-e4b9d840ec71
- https://blog.bitsrc.io/revolutionizing-micro-frontends-with-webpack-5-module-federation-and-bit-99ff81ceb0
# Events

Logout event

- User clicks logout
- Send analytics?
- Show notification?
- Tell Profile Page?
- Tell Cart Widget?
- Tell ...?
Pub/sub
Logout!


Pub/sub
Logout!





Some Challenges:
- Event naming and payload definitions get complicated
- Fundamental integration (i.e. longterm commitment!)
- Event chaining can be difficult
- Eg. call1, call2, call3
- Eg. call1_pending, call1_success, call1_fail
Suggested Libs
- useReducer hook
- Vegemite
- Redux Toolkit
- Redux Sagas
THANK YOU
Questions?
"Good architecture has big benefits, but it needs: education, discipline, and enforcement. That is part of the Sr./Staff engineer role."
Big Apps
By solsen-tl
Big Apps
- 181