├── .gitignore ├── images ├── components_architecture.png └── dependencies.png └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | -------------------------------------------------------------------------------- /images/components_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasteCleaner/frontend-system-design-framework/010f64fb941d913f51cd9b78c436b49b422879f7/images/components_architecture.png -------------------------------------------------------------------------------- /images/dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasteCleaner/frontend-system-design-framework/010f64fb941d913f51cd9b78c436b49b422879f7/images/dependencies.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Frontend System Design Framework 2 | 3 | ## Plan 4 | * [General requirements](#general-requirements) 5 | * [Functional requirements](#functional-requirements) 6 | * [Components architecture](#components-architecture) 7 | * [Dependencies](#dependencies) 8 | * [Data entities](#data-entities) 9 | * [API design](#api-design) 10 | * [Store design](#store-design) 11 | * [Optimization](#optimisation) 12 | * [Accessibility](#accessibility) 13 | * [Distribution](#distribution) 14 | 15 | ## General requirements 16 | In the first section we should define the scope of the project and the general requirements. The questions that will help us define the right architecture: 17 | - What the product should we have in the end 18 | - What the typical user flow 19 | 20 | Example _(imagine we are designing Instagram)_: 21 | 22 | > The user should be able to 23 | > - upload media content (photo/video) 24 | > - follow friends 25 | > - see his friends' photos in the feed 26 | > - add comments under the photo 27 | > - add likes 28 | > - ... 29 | 30 | ## Functional requirements 31 | Here we should define more technical requirements about how should our product work. 32 | 33 | Questions to ask: 34 | - on which devices should it work 35 | - project-specific question: 36 | - should it have an infinity scroll 37 | - should it have an offline mode 38 | - should it have a real-time update 39 | - should it have configuration (make sense if we design module) 40 | - requirements about accessibility 41 | 42 | ## Components Architecture 43 | In this section, it makes sense to draw basic UI parts to later refer to those pieces defining state and components API. 44 | 45 | ![Components architecture example](images/components_architecture.png) 46 | 47 | It shouldn't be like a final design. Just high-level blocks that show how you see the final product conceptually. 48 | 49 | ## Dependencies 50 | After preparing and agreeing on the architectural vision of all important parts of our application, we should draw a dependencies graph for them. 51 | 52 | ![Dependencies graph](images/dependencies.png) 53 | 54 | As we can see here, it defines what approximately the component hierarchy will look like. 55 | 56 | ## Data entities 57 | Now it's time to discuss the endpoints we need to make our system work. But before that, let us choose the technology we will use to connect the frontend to the backend. 58 | Let us take a look at what options we have: 59 | * REST API 60 | * GraphQL 61 | * Websocket 62 | * Long polling 63 | * SSE 64 | * Something else? 65 | 66 | But how do we decide what to use? What should we consider when making our decision? 67 | Let us compare different options and choose the most appropriate one based on our requirements. 68 | 69 | **REST API:** 70 | * ✅ http benefits 71 | * ✅ http2 compatibility 72 | * ✅ simplicity 73 | * ✅ easy to load balance 74 | * ❗ can have long latency 75 | * ❗ connections timeouts 76 | * ❗ traffic overhead 77 | 78 | **GraphQL:** 79 | * ✅ nice modern API 80 | * ✅ type safety 81 | * ✅ advanced caching tools 82 | * ✅ http benefits 83 | * ✅ http2 compatibility 84 | * ✅ easy to load balance 85 | * ❗ can have long latency 86 | * ❗ connections timeouts 87 | * ❗ traffic overhead 88 | * ❗ possibility to "DDoS" server _(in comparison to REST, which has the static interface of response, GQL offers clients to decide what data should be selected)_ 89 | 90 | **Websocket:** 91 | * ✅ duplex communication 92 | * ✅ super fast 93 | * ❗ expensive 94 | * ❗ http2 compatibility 95 | * ❗ load-balancing problem 96 | * ❗ need to do stuff to get http2 benefits 97 | * ❗ firewall/proxies problem 98 | 99 | **Long polling** 100 | * ✅ http benefits 101 | * ✅ simplicity 102 | * ❗ can have long latency 103 | * ❗ connections timeouts 104 | * ❗ traffic overhead 105 | 106 | **SSE** 107 | * ✅ http2 benefits (gzipping, multiplexing…) 108 | * ✅ receive only necessary information as text 109 | * ✅ efficiency - don’t waste resources 110 | * ✅ easy to load balance 111 | * ❗ weird api 112 | * ❗ unidirectional (we can’t send, only receive data) 113 | * ❗ only text data 114 | 115 | > Maybe the list here is not quite complete, but I will try to update it as soon as I know something new. 116 | 117 | So we can easily decide what we should choose based on our requirements. For example - do we need real time in our application? If yes - should it be bidirectional real-time? 118 | If we need unidirectional messaging - then we can consider SSE, in other cases Websocket makes more sense. 119 | 120 | After we have made a decision about the technology, it makes sense to define the endpoints we need and the data structures we want to work with. 121 | 122 | **Examples of endpoints we might have:** 123 | ``` 124 | login(email, password): Token 125 | posts(token, { limit, cursor }): Post[] 126 | addPost(token, { message, media }): Post 127 | addComment(token, { parentId, text, media }): Comment 128 | ``` 129 | 130 | ## API design 131 | This section is usually make sense in case we are designing a component rather than a service. For example, our task is to build a reusable DataTable or Calendar component. 132 | We are building a component that will be used by other developers, and we need to cover as many use cases as possible. Also, our component should be customizable and extensible. 133 | 134 | For example, let us design an API for the Calendar component. It could look like the following: 135 | ```typescript 136 | type Calendar = { 137 | calendarType: "month" | "week"; 138 | weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; 139 | onMonthChange?: (month: Date) => void; 140 | onWeekChange?: (week: Date) => void; 141 | onDayClick?: (day: Date) => void; 142 | renderDay?: (day: Date) => HTMLElement; 143 | actions?: { 144 | nextMonthButton?: boolean; 145 | prevMonthButton?: boolean; 146 | monthSelector?: boolean; 147 | yearSelector?: boolean; 148 | monthSlider?: boolean; 149 | weekSlider?: boolean; 150 | }; 151 | classes?: { 152 | root?: string; 153 | prevButton?: string; 154 | nextButton?: string; 155 | currentMonth?: string; 156 | week?: string; 157 | day: string; 158 | monthSlider?: boolean; 159 | weekSlider?: boolean; 160 | }; 161 | }; 162 | ``` 163 | 164 | 165 | ## Store design 166 | The next important section is a store design. We should define how we will store and work with data in our application. To define it I also recommend to use something like typescript types. 167 | 168 | For example, let us see what the store of our posts application might look like: 169 | 170 | ```typescript 171 | type User = { 172 | id: string; 173 | firstName: string; 174 | lastName: string; 175 | image?: string; 176 | }; 177 | 178 | type MediaContent = { 179 | type: "photo" | "video"; 180 | url: string; 181 | name?: string; 182 | }; 183 | 184 | type Comment = { 185 | id: string; 186 | author: User; 187 | text: string; 188 | media?: MediaContent[]; 189 | likes: number; 190 | } 191 | 192 | type Post = { 193 | id: string; 194 | author: User; 195 | text: string; 196 | media?: MediaContent[]; 197 | likes: number; 198 | retweets: number; 199 | comments: Comment[]; 200 | }; 201 | 202 | type Store = { 203 | user: User; // we keep data about authenticated user 204 | posts: Post[]; // currently received posts 205 | cursor?: string; // we should have last received post id as an cursor for pagination 206 | }; 207 | ``` 208 | 209 | Here is a basic store definition where we have defined the main entities in our application. From this point, we can think about some further optimizations. 210 | 211 | For example, let us imagine that we have requirements to have real-time updates for Likes in the post cards. This means that we have a websocket or SSE subscription and receive messages as follows: 212 | ```json 213 | { 214 | "type": "likesUpdate", 215 | "payload": { 216 | "postId": "abc123", 217 | "likes": 256 218 | } 219 | } 220 | ``` 221 | 222 | As we can see here, our store design is not optimal, as it is now necessary to find post in an array and then update the likes there. We could make a map to keep such statistics separate, for example: 223 | 224 | ```typescript 225 | type Store = { 226 | user: User; 227 | posts: Post[]; 228 | cursor?: string; 229 | postsLikes: Record; 230 | }; 231 | ``` 232 | 233 | Now we can easily update the likes for each post in O(1) and read the value with the same complexity. In a similar way, we can easily highlight all the data flow challenges and solve them. 234 | 235 | ## Optimization 236 | The next important part we should think about is optimization. Here I have prepared the main points that we should discuss in this section: 237 | 238 | **Network** 239 | * http 2 240 | * multiplexing 241 | * multiple connections 242 | * bundle splitting _(main bundle, vendor bundles etc.)_ 243 | * es6 bundle for modern devices 244 | * webp for images (and fallback to png) 245 | * minify resources 246 | * non-critical resources with link ‘preconnect’ 247 | * debouncing requests 248 | * caching 249 | * server cache 250 | * browser cache 251 | * store something in app 252 | * gzip 253 | * throttle 254 | * [brotli](https://github.com/google/brotli) 255 | * use CDN 256 | 257 | **Rendering** 258 | * inline critical resources and put inside page 259 | * non-critical resources - defer mode 260 | * load ‘analytics’ scripts later 261 | * SSR 262 | 263 | **DOM** 264 | * virtualization 265 | * have limited amount of nodes _(like virtual scroll technique)_ 266 | * soft-update _(don't delete nodes - update them)_ 267 | * perception _(use placeholders)_ 268 | 269 | **CSS** 270 | * CSS animation _(instead js)_ 271 | * avoid reflow 272 | * use css naming convention like BEM _(to avoid complex nested selectors)_ 273 | 274 | **JS** 275 | * do stuff async 276 | * web workers for complex staff 277 | * do some operation on server side 278 | * ship as fewer polyfills as we can 279 | * service workers 280 | 281 | ## Accessibility 282 | Very often, people with disabilities are simply ignored in web services. This is also an important issue to discuss. 283 | Here I have prepared some points to talk about: 284 | 285 | * keyboard navigation 286 | * list of shortcuts 287 | * tappable items 288 | * close shortcut 289 | * main functionality shortcuts 290 | * visual optimisation _(we should use rems instead of px and so on)_ 291 | * screen reader friendly _(aria-live attributes for fields, aria-role's and so on)_ 292 | * color schemas _(for people with color disabilities)_ 293 | * images should have correct alt attribute 294 | * semantic with HTML5 295 | 296 | ## Distribution 297 | This small section only makes sense if we are designing a specific type of system. For example: 298 | * reusable component 299 | * embeddable script 300 | 301 | So we just need to specify how we want to deliver our package to the customer. Will it be available in a private registry, or maybe we should specify a process to deliver it to a CDN and have versioning. 302 | --------------------------------------------------------------------------------