Hello, I'm Mateusz Roth. Versatile Software Engineer from 🇵🇱🇪🇺, specialized in JavaScript, TypeScript, Node.js, React. I'm eager to work with Node.js, Python, Golang, serverless or IoT.
Open-minded, friendly person that loves learning.

Software Engineering - architecture and software design

Truths

  • Product teams focusing on velocity may argue that in the future they will reach a place where they have time to circle back to fix technical debt. This is never true, there is always pressure. A team needs to consciously decide to make stability a part of value delivery, otherwise lack of stability will become part of the team’s culture. source
  • A mistake early on in the project regarding the architecture may cost a lot in terms of time spent refactoring the codebase later. source

Qualities of well written code

Write a code like someone after you who you don’t know would read the code.

Well written code is easy to maintain, easy to test, very cohesive, decoupled and redable.

Qualities

  • readability / understandability (pl. czytelność, zrozumiałość) vs obscurity (pl. zaciemnienie)
  • clean, readable and concise code - pl. czysty, czytelny, zwięzły kod
  • maintainability (pl. utrzymywalność)
  • reliability / testability (pl. niezawodność, testowalność)
  • complexity (pl. złożoność)
  • reusability - decoupled and reusable code is preferred
  • coupling and code interdependence
  • performance
  • https://en.wikipedia.org/wiki/No_Silver_Bullet essential vs accident complexity
  • robustness - the ability of a computer system to cope with errors during execution and cope with erroneous input
  • concise vs explicit
  • too much abstractions are bad

PEP20 — The Zen of Python
https://www.python.org/dev/peps/pep-0020/

System infrastructure qualities

  • reliability - pl. niezawodność
  • scalability - pl. skalowalność
  • performance - pl. wydajność
  • stability - pl. stabilność
  • flexibility - pl. jak łatwo się zmienia np. w przypadku mikroserwisów ilość jednostek jednego komponentu/serwisu, czy np. zmiana usługodawcy

Common software development problems

  • business pressures
  • developer turnover
  • code entropy
  • developers productivity
  • errors tracking and detecting
  • infrastructure cost and predicting spikes
  • development cost
  • enterprise level app - is computer software used to satisfy the needs of an organization rather than individual users

Languages features

  • pros if language offers excellent code analysis support and comprehensive editors/IDEs
  • pros if developing, refactoring and debugging is fast and easy
  • pros if language offers strict type system and compile-time error checks, C# is more potent than JavaScript

Codebase architecture approaches

  • Hexagonal Architecture
  • The Clean Architecture:
    • (Recommended architecture approaches) They all have the same objective, which is the separation of concerns. They all achieve this separation by dividing the software into layers. Each has at least one layer for business rules, and another for interfaces.
    • Independent of Frameworks
    • Testable
    • Independent of UI
    • Independent of Database
    • Independent of any external agency
    • Overall: The Dependency Rule always applies. Source code dependencies always point inwards. As you move inwards the level of abstraction increases. The outermost circle is low level concrete detail. As you move inwards the software grows more abstract, and encapsulates higher level policies. The inner most circle is the most general.

GoF Rules

  • Program to an ‘interface’, not an ‘implementation’.” (Gang of Four 1995:18)

    • clients remain unaware of the specific types of objects they use, as long as the object adheres to the interface
    • clients remain unaware of the classes that implement these objects; clients only know about the abstract class(es) defining the interface
    • Use of an interface also leads to dynamic binding and polymorphism, which are central features of object-oriented programming.
  • Composition over inheritance: “Favor ‘object composition’ over ‘class inheritance’.” (Gang of Four 1995:20)

    • The authors refer to inheritance as white-box reuse, with white-box referring to visibility, because the internals of parent classes are often visible to subclasses. In contrast, the authors refer to object composition (in which objects with well-defined interfaces are used dynamically at runtime by objects obtaining references to other objects) as black-box reuse because no internal details of composed objects need be visible in the code using them.
    • The authors discuss the tension between inheritance and encapsulation at length and state that in their experience, designers overuse inheritance (Gang of Four 1995:20).
    • “Because inheritance exposes a subclass to details of its parent’s implementation, it’s often said that ‘inheritance breaks encapsulation’“. (Gang of Four 1995:19).
    • They warn that the implementation of a subclass can become so bound up with the implementation of its parent class that any change in the parent’s implementation will force the subclass to change.
    • Furthermore, they claim that a way to avoid this is to inherit only from abstract classes—but then, they point out that there is minimal code reuse.
    • Using inheritance is recommended mainly when adding to the functionality of existing components, reusing most of the old code and adding relatively small amounts of new code.

Redux

Overview

  • keeps the state of your app in a single store
  • terms to learn: actions, reducers, action creators, dispatch, middleware, pure functions, immutability, selectors
  • reducer’s job is to return a new state, even if that state is unchanged from the current one
  • do not mutate the state. State is immutable

Sample reducer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function reducer(state = initialState, action) {
switch(action.type) {
case 'INCREMENT':
return {
count: state.count + 1
};
case 'DECREMENT':
return {
count: state.count - 1
};
default:
return state;
}
}

Terms and methods

connect method

  • Using the connect function that comes with Redux, you can plug any component into Redux’s data store, and the component can pull out the data it requires.

Example usage

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import { connect } from 'react-redux';

const Avatar = ({ user }) => (
<img src={user.avatar}/>
);

const mapStateToProps = state => ({
user: state.user
});

export { Avatar };
export default connect(mapStateToProps)(Avatar);

Sources

React - code reusability

Code reusability

Higher Order Components

Comparison to Render Props and React Hooks:

https://medium.com/simply/comparison-hocs-vs-render-props-vs-hooks-55f9ffcd5dc6

HOCs pitfalls

Don’t Use HOCs Inside the render Method

Apply HOCs outside the component definition so that the resulting component is created only once. Then, its identity will be consistent across renders. This is usually what you want, anyway.

Docs: https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method

Class Static Methods Must Be Copied Over

Docs: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over

When you use HOC or just wrap a component by a function, static methods are not being exposed by that HOC/function:

1
2
3
4
5
6
7
// Define a static method
WrappedComponent.staticMethod = function() {/*...*/}
// Now apply a HOC
const EnhancedComponent = enhance(WrappedComponent);

// The enhanced component has no static method
typeof EnhancedComponent.staticMethod === 'undefined' // true

How to solve: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over

Refs Aren’t Passed Through

If you add a ref to an element whose component is the result of a HOC, the ref refers to an instance of the outermost container component, not the wrapped component.

How to solve: https://reactjs.org/docs/higher-order-components.html#static-methods-must-be-copied-over

Types of HOCs

Render Props

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
interface InjectedCounterProps {
value: number;
onIncrement(): void;
onDecrement(): void;
}

interface MakeCounterProps {
minValue?: number;
maxValue?: number;
children(props: InjectedCounterProps): JSX.Element;
}

interface MakeCounterState {
value: number;
}

class MakeCounter extends React.Component<MakeCounterProps, MakeCounterState> {
state: MakeCounterState = {
value: 0,
};

increment = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.maxValue
? prevState.value
: prevState.value + 1,
}));
};

decrement = () => {
this.setState(prevState => ({
value:
prevState.value === this.props.minValue
? prevState.value
: prevState.value - 1,
}));
};

render() {
return this.props.children({
value: this.state.value,
onIncrement: this.increment,
onDecrement: this.decrement,
});
}
}

Usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface CounterProps {
style: React.CSSProperties;
minValue?: number;
maxValue?: number;
}

const Counter = (props: CounterProps) => (
<MakeCounter minValue={props.minValue} maxValue={props.maxValue}>
{injectedProps => (
<div style={props.style}>
<button onClick={injectedProps.onDecrement}> - </button>
{injectedProps.value}
<button onClick={injectedProps.onIncrement}> + </button>
</div>
)}
</MakeCounter>
);

Issues of

  • Firstly, there is an issue with separation of concerns with Render Props; if you wrap a component with a render prop in the same component, it will make it more difficult to test the two in isolation. May be fixed by creating more boilerplate code and creating separate component for what is rendered inside the render prop
  • Secondly, as the props are injected in the render function of component, you cannot make use of them in the lifecycle methods
  • Source: https://medium.com/@jrwebdev/react-render-props-in-typescript-b561b00bc67c

Render Prop vs HOC

  • In the end, the tradeoff between HOCs and render prop components comes down to flexibility vs convenience. This can be solved by writing render prop components first, then generating HOCs from them, which gives the consumer the power to choose between the two.
  • As far as TypeScript goes, there is no doubt that typing HOCs is much more difficult.
  • Prior to React v16.8.0, I’d recommend sticking with render prop components for the flexibility and simplicity of typing, and if the need arises, such as building a reusable library of components, or for render prop components that are simple and/or widely used across a project, I would only then generate a HOC from them.
  • in React v16.8.0, I would strongly recommend using hooks over both higher-order components or render props where possible, as they are much simpler to type.
  • Source: https://medium.com/@jrwebdev/react-render-props-in-typescript-b561b00bc67c

React Hooks

FAQ

Custom Hook versus HOC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { useState, useEffect } from "react";

function useDataFetching(dataSource) {
const [loading, setLoading] = useState(true);
const [results, setResults] = useState([]);
const [error, setError] = useState("");

useEffect(() =>
{
async function fetchData() {
try {
const data = await fetch(dataSource);
const json = await data.json();

if (json) {
setLoading(false);
setResults(json);
}
} catch (error) {
setLoading(false);
setError(error.message);
}

setLoading(false);
}

fetchData();
}, [dataSource]);

return {
error,
loading,
results
};
}

export default useDataFetching;

source and comparison to standard HOC: https://dev.to/gethackteam/from-higher-order-components-hoc-to-react-hooks-2bm9

Use only Hooks for global state management

You can use useContext and useReducer to create a Redux-like store management in your app. Checkout https://medium.com/simply/state-management-with-react-hooks-and-context-api-at-10-lines-of-code-baf6be8302c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { StateProvider } from '../state';

const App = () =>
{
const initialState = {
theme: { primary: 'green' }
};

const reducer = (state, action) =>
{
switch (action.type) {
case 'changeTheme':
return {
...state,
theme: action.newTheme
};

default:
return state;
}
};

return (
<StateProvider initialState={initialState} reducer={reducer}>
// App content ...
</StateProvider>
);
}

usage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useStateValue } from './state';

const ThemedButton = () => {
const [{ theme }, dispatch] = useStateValue();
return (
<Button
primaryColor={theme.primary}
onClick={() => dispatch({
type: 'changeTheme',
newTheme: { primary: 'blue'}
})}
>
Make me blue!
</Button>
);
}

Message brokers

Message brokers

  • a message broker accepts and forwards messages
  • allows applications to communicate via a queuing mechanism
  • a program that sends messages is a producer
  • a queue is essentially a large message buffer
  • many producers can send messages that go to one queue, and many consumers can try to receive data from one queue
  • a consumer is a program that mostly waits to receive messages

Use cases

  • Point-to-point messaging
  • Publish/subscribe - event-driven architecture-based system, where applications have fewer dependencies between each other
  • Long-running tasks and crucial API like preparing files to download
  • Microservices - to create event-based communication and use the message broker together with publish/subscribe pattern instead of REST APIs
  • Transactional systems - where we have several actions that need to be made after each previous one completes

Advantages

  • Improved system performance by introducing asynchronous processing
  • Increased reliability by guaranteeing the transmission of messages
    • In case of consumer failure, it can redeliver the message immediately or after some specified time

Disadvantages

  • Increased system complexity
    • maintaining the network between components or security issues
    • problem related to eventual consistency where some components could not have up-to-date data until the messages are propagated and processed
  • Debugging can be harder
  • There’s a steep learning curve at first
    • size of queues and messages, the behavior of queues, delivery settings or messages TTL

Amazon SQS & SNS

  • you can combine Amazon SNS power of publishing to multiple recipients with Amazon SQS durable queues

Examples

Sources

Microservices

Microservices

  • a software design architecture that breaks apart monolithic systems
  • applications are built as collections of loosely coupled services
  • data moves between microservices using “dumb pipes” such as an event broker and/or a lightweight protocol like REST

Advantages

  • teams can develop, maintain, and deploy each microservice independently
  • scalability, you can scale them separately; the cost of scaling is comparatively less than the monolithic architecture
  • faster development cycles - easier deployment and debugging
  • speed up your CI/CD pipeline against big monolithic apps
  • it’s easier to maintain and debug a lightweight microservice than a complex application
  • isolated services have a better failure tolerance

Disadvantages

  • communication between microservices can mean poorer performance, as sending messages back and forth comes with a certain overhead
  • microservice has all the associated complexities of the distributed system
  • complex testing over a distributed environment; while unit testing may be easier with microservices, integration testing is not
  • up-front costs may be higher with microservices

Sources

Redis

Redis

  • Redis is an in-memory data structure store that can be used as a database, cache, or publish/subscribe client.
  • In terms of WebSockets there are some ways of “sharing” the pool of open connections between many instances. One way is to use Redis’ publish/subscribe mechanism to forward emitted events between all instances of the application to make sure each open connection receives them.

Security

Security

  • File inclusion vulnerability - doesn’t upload files like .exe or some scripts than can be accidentally run on server
  • SQL injection - clean user input
  • CSRF (Cross site request forgery) and cookies
    • In a cross-site request forgery attack, the attacker tries to force/trick you into making a request which you did not intend. This could be sending you a link that makes you involuntarily change your password. A malicious link could look like that: https://security.stackexchange.com/account?new_password=abc123
    • The browser automatically sending cookies also has a big downside, which is CSRF attacks. In a CSRF attack, a malicious website takes advantage of the fact that your browser will automatically attach authentication cookies to requests to that domain and tricks your browser into executing a request.
    • cookies make it more difficult for non-browser based applications (like mobile to tablet apps) to consume your API.
    • JWTs are stored as cookies on many occasions, and cookies are vulnerable/susceptible to CSRF (Cross-site Request Forgery) attacks. One of the many ways to prevent CSRF attacks is to ensure that your cookie is accessible by only your domain. As a developer, ensure that necessary CSRF protections are put in place to avoid these attacks, regardless of the use of JWTs.
  • XSS
    • In a cross-site scripting attack, the attacker makes you involuntarily execute client-side code, most likely Javascript. A typical reflected XSS attacking attempt could look like this: https://security.stackexchange.com/search?q="><script>alert(document.cookie)</script>
    • (Cross-site scripting): an attacker embeds a script in the victim site (the victim site is only vulnerable if inputs are not sanitized correctly), and the attacker’s script can do anything JavaScript is allowed to do on the page.
    • If you store JWT tokens in local storage, the attacker’s script could read those tokens, and also send those tokens to a server they control. In fact, a lot of people advocate that very sensitive data shouldn’t be stored in Web Storage because of XSS attacks

Authentication and Authorization

Authentication and Authorization

  • Authentication (pl. uwierzytelnianie, autentykacja) describes the process of claiming an identity. That’s what you do when you log in to a service with a username and password, you authenticate yourself.
  • Authorization on the other hand describes permission rules that specify the access rights of individual users and user groups to certain parts of the system, i.e. ACL.
  • In modern era, everyone is going for token based authentication instead of session based cookies.
  • A spreadsheet describing authentication techniques: (Authentication Techniques for APIs)[https://docs.google.com/spreadsheets/d/1tAX5ZJzluilhoYKjra-uHbMCZraaQkqIHl3RIQ8mVkM/edit#gid=0]

Authentication

  • When we do token-based authentication, such as OpenID, OAuth, or OpenID Connect, we receive an access_token (and sometimes id_token) from a trusted authority
  • Passport is an authentication middleware for Node.js
    • Packages
      • bcrypt for hashing user passwords
      • jsonwebtoken for signing tokens
      • passport-local for implementing local strategy
      • passport-jwt for retrieving and verifying JWTs

OAuth2

  • in horizontal scaling scenario you have to start replicating servers
    • for cookie based authentication has a disadvantage of need for central session storage system that all of your application servers have access to
      • Setting up and maintaining this type of distributed system involves in-depth technical knowledge and subsequently incurs higher financial costs
    • for JWT based authentication our application can scale easily because we can use tokens to access resources from different servers without worrying if the user was actually logged in on a particular server. You also save costs because you don’t need a dedicated server to store your sessions. Why? Because there are no sessions!

Authorization

JWT (JSON Web Tokens)

  • JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.
  • JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair using RSA.
  • JWTs consist of three parts separated by dots (.), which are:
    • Header
      • consists of two parts: the type of the token, which is JWT, and the hashing algorithm such as HMAC SHA256 or RSA
      • JSON is encoded to Base64Url
        1
        2
        3
        4
        {
        "alg": "HS256",
        "typ": "JWT"
        }
    • Payload
      • The payload contains all the required information about the user
      • do not store passwords here or secret data
        1
        2
        3
        4
        5
        {
        "sub": "1234567890",
        "name": "John Doe",
        "admin": true
        }
    • Signature
      • you have to take the encoded header, the encoded payload, a secret, the algorithm specified in the header, and sign that to create signature
        1
        2
        3
        4
        HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret)
  • can be sent through an URL, POST parameter, or inside an HTTP header
  • JWT sent over as an Authorization: Bearer <token> header is a stateless authentication mechanism as the user state is never saved in the server memory

Advantages

  • They are easy to scale horizontally
  • due to its size its transmission is fast
  • They are easier to maintain and debug
  • They have the ability to create truly RESTful Services
  • They have built-in expiration functionality
  • JSON Web Tokens are self-contained
  • JWT is a stateless authentication mechanism as the user state is never saved in the server memory
  • doesn’t matter which domains are serving your APIs, as Cross-Origin Resource Sharing (CORS) won’t be an issue as it doesn’t use cookies
  • a good way of securely transmitting information between parties

Disadvantages

Precautions

  • a great care must be taken to prevent security issues and you should not keep tokens longer than required
  • you also should not store sensitive session data in browser storage due to lack of security

Invalidation

  • Simply remove the token from the client
  • Create a token blocklist or whitelist
  • Just keep token expiry times short and rotate them often

While using cookies

  • Cookies/session id is not self contained. It is a reference token. During each validation the Gmail server needs to fetch the information corresponding to it.
    • Cookie/session based authentication doesn’t scale well
  • JWT is self contained. It is a value token. So during each validation the Gmail server does not needs to fetch the information corresponding to it. It is most suitable for Microservices Architecture
    • with JWTs REST API is stateless and therefore without side effects means that maintainability and debugging are made much easier
    • CORS - for an API to be served from one server and for the actual application to consume it from another. To make this happen, we need to enable Cross-Origin Resource Sharing (CORS). Since cookies can only be used for the domain from which they originated, they aren’t much help for APIs on different domains than the application. Using JWTs for authentication in this case ensures that the RESTful API is stateless

Use cases

  • Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token. Single Sign On is a feature that widely uses JWT nowadays, because of its small overhead and its ability to be easily used across different domains.

ACL / Roles and permissions

Sources

REST APIs

REST APIs

  • GET, POST (/book), DELETE, PUT (/book/:id) to perform CRUD
  • detailed explanation:
    • POST (create a resource or generally provide data)
    • GET (retrieve an index of resources or an individual resource)
    • PUT (create or replace a resource) - you send whole object to change
    • PATCH (update/modify a resource) - you send only specific properties to change
    • DELETE (remove a resource)
  • sample paths:
    • [POST] endpoint/users
    • [GET] endpoint/users (list users)
    • [GET] endpoint/users/:userId (get specific user)
    • [PATCH] endpoint/users/:userId (update the data for the specified user)
    • [DELETE] endpoint/users/:userId (remove the specified user)
    • [GET] /users/:userId/books/:bookId
      1
      2
      3
      Route path: /flights/:from-:to
      Request URL: http://localhost:3000/flights/LAX-SFO
      req.params: { "from": "LAX", "to": "SFO" }

HTTP

HTTP

HTTP 2.0: multiplexing, server push, header compression

  • In particular, HTTP/2 is much faster and more efficient than HTTP/1.1
  • In HTTP/2, developers have hands-on, detailed control over prioritization. This allows them to maximize perceived and actual page load speed to a degree that was not possible in HTTP/1.1
  • HTTP/2 is multiplexed, i.e., it can initiate multiple requests in parallel over a single TCP connection. As a result, web pages containing several elements are delivered over one TCP connection. These capabilities solve the head-of-line blocking problem in HTTP/1.1, in which a packet at the front of the line blocks others from being transmitted.
  • HTTP/2 uses header compression to reduce the overhead caused by TCP’s slow-start mechanism.
  • As opposed to HTTP/1.1, which keeps all requests and responses in plain text format, HTTP/2 uses the binary framing layer to encapsulate all messages in binary format, while still maintaining HTTP semantics, such as verbs, methods, and headers.
  • HTTP/2 allows a server to “push” content to a client before the client asks for it.
  • HTTP/3 runs over QUIC instead of TCP. QUIC is a faster and more secure transport layer protocol that is designed for the needs of the modern Internet.