[{"data":1,"prerenderedAt":6236},["ShallowReactive",2],{"home-writing":3},[4,1702,2349],{"id":5,"title":6,"body":7,"category":1688,"date":1689,"description":13,"excerpt":1690,"extension":1691,"image":1692,"keywords":1693,"meta":1694,"navigation":988,"path":1695,"readingTime":1696,"seo":1697,"slug":1698,"stem":1699,"tags":1700,"__hash__":1701},"blog\u002Fblog\u002Fopen-claw-secure-hardened-setup.md","Secure and hardened OpenClaw setup on a VPS",{"type":8,"value":9,"toc":1673},"minimark",[10,14,17,25,41,44,49,57,79,86,89,115,118,142,145,168,171,194,197,213,216,220,223,230,297,303,317,320,324,327,340,346,353,382,385,445,452,464,471,474,515,518,528,534,540,544,550,574,577,600,603,634,637,641,648,662,665,669,672,679,704,710,713,725,728,1119,1122,1136,1146,1153,1157,1162,1165,1177,1180,1191,1194,1203,1206,1210,1213,1229,1232,1255,1270,1273,1296,1299,1303,1306,1322,1325,1342,1349,1353,1356,1366,1369,1385,1391,1406,1409,1420,1427,1433,1602,1605,1627,1630,1634,1637,1640,1660,1663,1666,1669],[11,12,13],"p",{},"I have been experimenting with OpenClaw recently, and while the initial setup is fairly straightforward, the secure setup is where most of the interesting decisions actually are.",[11,15,16],{},"The difference between “it works” and “it is safe enough to leave running on a VPS” is not small. If you are running an agent that can read files, write code, install packages, and expose a gateway for device pairing, you really do not want to treat the box like a throwaway toy project.",[11,18,19,20,24],{},"So in this post I am going to walk through the setup I ended up with for OpenClaw on a VPS, but with an emphasis on ",[21,22,23],"strong",{},"hardening the machine first",", and only then installing the app. The main goal is simple:",[26,27,28,32,35,38],"ul",{},[29,30,31],"li",{},"lock down SSH",[29,33,34],{},"keep the OpenClaw gateway on loopback only",[29,36,37],{},"avoid exposing unnecessary ports",[29,39,40],{},"run the service in a way that survives rebuilds and reboots",[11,42,43],{},"This is not the only way to do it, but it is a pragmatic setup that I would be comfortable running.",[45,46,48],"h2",{"id":47},"first-harden-the-server-before-installing-anything","First: harden the server before installing anything",[11,50,51,52,56],{},"Start by connecting to your VPS as ",[53,54,55],"code",{},"root",".",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-bash shiki shiki-themes github-light github-dark","ssh root@YOUR_VPS_IP\n","bash","",[53,65,66],{"__ignoreMap":63},[67,68,71,75],"span",{"class":69,"line":70},"line",1,[67,72,74],{"class":73},"sScJk","ssh",[67,76,78],{"class":77},"sZZnC"," root@YOUR_VPS_IP\n",[11,80,81,82,85],{},"If this is the first time you connect, type ",[53,83,84],{},"yes"," at the SSH fingerprint prompt.",[11,87,88],{},"Once you are in, update the machine.",[58,90,92],{"className":60,"code":91,"language":62,"meta":63,"style":63},"apt-get update && apt-get upgrade -y\n",[53,93,94],{"__ignoreMap":63},[67,95,96,99,102,106,108,111],{"class":69,"line":70},[67,97,98],{"class":73},"apt-get",[67,100,101],{"class":77}," update",[67,103,105],{"class":104},"sVt8B"," && ",[67,107,98],{"class":73},[67,109,110],{"class":77}," upgrade",[67,112,114],{"class":113},"sj4cs"," -y\n",[11,116,117],{},"Then install the basic packages we need along the way.",[58,119,121],{"className":60,"code":120,"language":62,"meta":63,"style":63},"apt-get install -y git curl ca-certificates\n",[53,122,123],{"__ignoreMap":63},[67,124,125,127,130,133,136,139],{"class":69,"line":70},[67,126,98],{"class":73},[67,128,129],{"class":77}," install",[67,131,132],{"class":113}," -y",[67,134,135],{"class":77}," git",[67,137,138],{"class":77}," curl",[67,140,141],{"class":77}," ca-certificates\n",[11,143,144],{},"Now install Docker:",[58,146,148],{"className":60,"code":147,"language":62,"meta":63,"style":63},"curl -fsSL https:\u002F\u002Fget.docker.com | sh\n",[53,149,150],{"__ignoreMap":63},[67,151,152,155,158,161,165],{"class":69,"line":70},[67,153,154],{"class":73},"curl",[67,156,157],{"class":113}," -fsSL",[67,159,160],{"class":77}," https:\u002F\u002Fget.docker.com",[67,162,164],{"class":163},"szBVR"," |",[67,166,167],{"class":73}," sh\n",[11,169,170],{},"And after that, install the basic hardening tools:",[58,172,174],{"className":60,"code":173,"language":62,"meta":63,"style":63},"apt install ufw fail2ban unattended-upgrades -y\n",[53,175,176],{"__ignoreMap":63},[67,177,178,181,183,186,189,192],{"class":69,"line":70},[67,179,180],{"class":73},"apt",[67,182,129],{"class":77},[67,184,185],{"class":77}," ufw",[67,187,188],{"class":77}," fail2ban",[67,190,191],{"class":77}," unattended-upgrades",[67,193,114],{"class":113},[11,195,196],{},"Enable automatic security updates as well:",[58,198,200],{"className":60,"code":199,"language":62,"meta":63,"style":63},"dpkg-reconfigure -plow unattended-upgrades\n",[53,201,202],{"__ignoreMap":63},[67,203,204,207,210],{"class":69,"line":70},[67,205,206],{"class":73},"dpkg-reconfigure",[67,208,209],{"class":113}," -plow",[67,211,212],{"class":77}," unattended-upgrades\n",[11,214,215],{},"There is nothing OpenClaw-specific here yet, and that is the point. Before giving an AI agent a home on your VPS, make sure the VPS itself is not casually exposed.",[45,217,219],{"id":218},"configure-the-firewall-before-openclaw-exists","Configure the firewall before OpenClaw exists",[11,221,222],{},"This is one of the most important parts of the whole setup.",[11,224,225,226,229],{},"The gateway should ",[21,227,228],{},"not"," be reachable from the public internet. It should bind to loopback and only be reachable locally on the server. So before installing OpenClaw, configure the firewall to allow only SSH.",[58,231,233],{"className":60,"code":232,"language":62,"meta":63,"style":63},"ufw default deny incoming\nufw default allow outgoing\nufw allow 22\u002Ftcp comment 'SSH'\nufw enable\nufw status verbose\n",[53,234,235,249,262,278,286],{"__ignoreMap":63},[67,236,237,240,243,246],{"class":69,"line":70},[67,238,239],{"class":73},"ufw",[67,241,242],{"class":77}," default",[67,244,245],{"class":77}," deny",[67,247,248],{"class":77}," incoming\n",[67,250,252,254,256,259],{"class":69,"line":251},2,[67,253,239],{"class":73},[67,255,242],{"class":77},[67,257,258],{"class":77}," allow",[67,260,261],{"class":77}," outgoing\n",[67,263,265,267,269,272,275],{"class":69,"line":264},3,[67,266,239],{"class":73},[67,268,258],{"class":77},[67,270,271],{"class":77}," 22\u002Ftcp",[67,273,274],{"class":77}," comment",[67,276,277],{"class":77}," 'SSH'\n",[67,279,281,283],{"class":69,"line":280},4,[67,282,239],{"class":73},[67,284,285],{"class":77}," enable\n",[67,287,289,291,294],{"class":69,"line":288},5,[67,290,239],{"class":73},[67,292,293],{"class":77}," status",[67,295,296],{"class":77}," verbose\n",[11,298,299,300,302],{},"What you should ",[21,301,228],{}," do is this:",[58,304,306],{"className":60,"code":305,"language":62,"meta":63,"style":63},"ufw allow 18789\u002Ftcp\n",[53,307,308],{"__ignoreMap":63},[67,309,310,312,314],{"class":69,"line":70},[67,311,239],{"class":73},[67,313,258],{"class":77},[67,315,316],{"class":77}," 18789\u002Ftcp\n",[11,318,319],{},"That would expose the OpenClaw gateway directly to the internet, which defeats the point of keeping it local in the first place.",[45,321,323],{"id":322},"create-a-separate-user-and-stop-using-root","Create a separate user and stop using root",[11,325,326],{},"Next, create a dedicated user for running and managing OpenClaw.",[58,328,330],{"className":60,"code":329,"language":62,"meta":63,"style":63},"adduser openclaw\n",[53,331,332],{"__ignoreMap":63},[67,333,334,337],{"class":69,"line":70},[67,335,336],{"class":73},"adduser",[67,338,339],{"class":77}," openclaw\n",[11,341,342,343,56],{},"Set a password, skip the optional name and phone fields, and confirm with ",[53,344,345],{},"Y",[11,347,348,349,352],{},"Then give that user ",[53,350,351],{},"sudo"," and Docker access:",[58,354,356],{"className":60,"code":355,"language":62,"meta":63,"style":63},"usermod -aG sudo openclaw\nusermod -aG docker openclaw\n",[53,357,358,371],{"__ignoreMap":63},[67,359,360,363,366,369],{"class":69,"line":70},[67,361,362],{"class":73},"usermod",[67,364,365],{"class":113}," -aG",[67,367,368],{"class":77}," sudo",[67,370,339],{"class":77},[67,372,373,375,377,380],{"class":69,"line":251},[67,374,362],{"class":73},[67,376,365],{"class":113},[67,378,379],{"class":77}," docker",[67,381,339],{"class":77},[11,383,384],{},"Copy over your SSH key so you can log in as the new user:",[58,386,388],{"className":60,"code":387,"language":62,"meta":63,"style":63},"mkdir -p \u002Fhome\u002Fopenclaw\u002F.ssh\ncp \u002Froot\u002F.ssh\u002Fauthorized_keys \u002Fhome\u002Fopenclaw\u002F.ssh\u002F\nchown -R openclaw:openclaw \u002Fhome\u002Fopenclaw\u002F.ssh\nchmod 700 \u002Fhome\u002Fopenclaw\u002F.ssh\nchmod 600 \u002Fhome\u002Fopenclaw\u002F.ssh\u002Fauthorized_keys\n",[53,389,390,401,412,425,435],{"__ignoreMap":63},[67,391,392,395,398],{"class":69,"line":70},[67,393,394],{"class":73},"mkdir",[67,396,397],{"class":113}," -p",[67,399,400],{"class":77}," \u002Fhome\u002Fopenclaw\u002F.ssh\n",[67,402,403,406,409],{"class":69,"line":251},[67,404,405],{"class":73},"cp",[67,407,408],{"class":77}," \u002Froot\u002F.ssh\u002Fauthorized_keys",[67,410,411],{"class":77}," \u002Fhome\u002Fopenclaw\u002F.ssh\u002F\n",[67,413,414,417,420,423],{"class":69,"line":264},[67,415,416],{"class":73},"chown",[67,418,419],{"class":113}," -R",[67,421,422],{"class":77}," openclaw:openclaw",[67,424,400],{"class":77},[67,426,427,430,433],{"class":69,"line":280},[67,428,429],{"class":73},"chmod",[67,431,432],{"class":113}," 700",[67,434,400],{"class":77},[67,436,437,439,442],{"class":69,"line":288},[67,438,429],{"class":73},[67,440,441],{"class":113}," 600",[67,443,444],{"class":77}," \u002Fhome\u002Fopenclaw\u002F.ssh\u002Fauthorized_keys\n",[11,446,447,448,451],{},"Now test it in a ",[21,449,450],{},"new terminal tab",", while keeping your root session open:",[58,453,455],{"className":60,"code":454,"language":62,"meta":63,"style":63},"ssh openclaw@YOUR_VPS_IP\n",[53,456,457],{"__ignoreMap":63},[67,458,459,461],{"class":69,"line":70},[67,460,74],{"class":73},[67,462,463],{"class":77}," openclaw@YOUR_VPS_IP\n",[11,465,466,467,470],{},"If you see a shell prompt for ",[53,468,469],{},"openclaw",", you are good.",[11,472,473],{},"Only after that should you lock down root SSH access:",[58,475,477],{"className":60,"code":476,"language":62,"meta":63,"style":63},"echo \"PermitRootLogin no\" >> \u002Fetc\u002Fssh\u002Fsshd_config\necho \"PasswordAuthentication no\" >> \u002Fetc\u002Fssh\u002Fsshd_config\nsystemctl restart ssh\n",[53,478,479,493,504],{"__ignoreMap":63},[67,480,481,484,487,490],{"class":69,"line":70},[67,482,483],{"class":113},"echo",[67,485,486],{"class":77}," \"PermitRootLogin no\"",[67,488,489],{"class":163}," >>",[67,491,492],{"class":77}," \u002Fetc\u002Fssh\u002Fsshd_config\n",[67,494,495,497,500,502],{"class":69,"line":251},[67,496,483],{"class":113},[67,498,499],{"class":77}," \"PasswordAuthentication no\"",[67,501,489],{"class":163},[67,503,492],{"class":77},[67,505,506,509,512],{"class":69,"line":264},[67,507,508],{"class":73},"systemctl",[67,510,511],{"class":77}," restart",[67,513,514],{"class":77}," ssh\n",[11,516,517],{},"And verify:",[58,519,520],{"className":60,"code":61,"language":62,"meta":63,"style":63},[53,521,522],{"__ignoreMap":63},[67,523,524,526],{"class":69,"line":70},[67,525,74],{"class":73},[67,527,78],{"class":77},[11,529,530,531,56],{},"This should now fail with ",[53,532,533],{},"Permission denied",[11,535,536,537,539],{},"From this point on, use the ",[53,538,469],{}," user only.",[45,541,543],{"id":542},"clone-the-repo-and-create-persistent-directories","Clone the repo and create persistent directories",[11,545,546,547,549],{},"Log in as the ",[53,548,469],{}," user and update packages once more:",[58,551,553],{"className":60,"code":552,"language":62,"meta":63,"style":63},"sudo apt-get update && sudo apt-get upgrade -y\n",[53,554,555],{"__ignoreMap":63},[67,556,557,559,562,564,566,568,570,572],{"class":69,"line":70},[67,558,351],{"class":73},[67,560,561],{"class":77}," apt-get",[67,563,101],{"class":77},[67,565,105],{"class":104},[67,567,351],{"class":73},[67,569,561],{"class":77},[67,571,110],{"class":77},[67,573,114],{"class":113},[11,575,576],{},"Then clone the repository:",[58,578,580],{"className":60,"code":579,"language":62,"meta":63,"style":63},"git clone https:\u002F\u002Fgithub.com\u002Fopenclaw\u002Fopenclaw.git\ncd openclaw\n",[53,581,582,593],{"__ignoreMap":63},[67,583,584,587,590],{"class":69,"line":70},[67,585,586],{"class":73},"git",[67,588,589],{"class":77}," clone",[67,591,592],{"class":77}," https:\u002F\u002Fgithub.com\u002Fopenclaw\u002Fopenclaw.git\n",[67,594,595,598],{"class":69,"line":251},[67,596,597],{"class":113},"cd",[67,599,339],{"class":77},[11,601,602],{},"Create the directories that should survive container rebuilds:",[58,604,606],{"className":60,"code":605,"language":62,"meta":63,"style":63},"sudo mkdir -p \u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fworkspace\nsudo chown -R openclaw:openclaw \u002Fhome\u002Fopenclaw\u002F.openclaw\n",[53,607,608,620],{"__ignoreMap":63},[67,609,610,612,615,617],{"class":69,"line":70},[67,611,351],{"class":73},[67,613,614],{"class":77}," mkdir",[67,616,397],{"class":113},[67,618,619],{"class":77}," \u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fworkspace\n",[67,621,622,624,627,629,631],{"class":69,"line":251},[67,623,351],{"class":73},[67,625,626],{"class":77}," chown",[67,628,419],{"class":113},[67,630,422],{"class":77},[67,632,633],{"class":77}," \u002Fhome\u002Fopenclaw\u002F.openclaw\n",[11,635,636],{},"I prefer keeping this data outside the container so configuration, auth state, and workspace files do not disappear when rebuilding images or replacing containers.",[45,638,640],{"id":639},"protect-the-environment-file","Protect the environment file",[11,642,643,644,647],{},"Once you create your ",[53,645,646],{},".env",", lock it down immediately:",[58,649,651],{"className":60,"code":650,"language":62,"meta":63,"style":63},"chmod 600 .env\n",[53,652,653],{"__ignoreMap":63},[67,654,655,657,659],{"class":69,"line":70},[67,656,429],{"class":73},[67,658,441],{"class":113},[67,660,661],{"class":77}," .env\n",[11,663,664],{},"That is just a small step, but it is one of those small steps that should always be there.",[45,666,668],{"id":667},"why-the-default-docker-compose-file-is-not-enough","Why the default Docker Compose file is not enough",[11,670,671],{},"This is where the setup starts getting more opinionated.",[11,673,674,675,678],{},"The default ",[53,676,677],{},"docker-compose.yml"," is not what I would call a hardened setup. There are a few things we want to change:",[680,681,682,689,692,695,698],"ol",{},[29,683,684,685,688],{},"We want the gateway to build from the local ",[53,686,687],{},"Dockerfile",", because we are going to tweak that file.",[29,690,691],{},"We want host networking so device pairing works reliably.",[29,693,694],{},"We want the gateway bound to loopback, not something reachable from outside.",[29,696,697],{},"We want restart policies so the service comes back after reboot or crashes.",[29,699,700,701,703],{},"We do ",[21,702,228],{}," want leftover Anthropic session variables hanging around if we are not using them.",[11,705,706,707,56],{},"If you want to edit comfortably, this is one of those cases where VS Code Remote SSH is much nicer than editing everything with ",[53,708,709],{},"nano",[11,711,712],{},"The file we are changing is:",[58,714,716],{"className":60,"code":715,"language":62,"meta":63,"style":63},"nano \u002Fhome\u002Fopenclaw\u002Fopenclaw\u002Fdocker-compose.yml\n",[53,717,718],{"__ignoreMap":63},[67,719,720,722],{"class":69,"line":70},[67,721,709],{"class":73},[67,723,724],{"class":77}," \u002Fhome\u002Fopenclaw\u002Fopenclaw\u002Fdocker-compose.yml\n",[11,726,727],{},"And this is the version I ended up using:",[58,729,733],{"className":730,"code":731,"language":732,"meta":63,"style":63},"language-yaml shiki shiki-themes github-light github-dark","services:\n  openclaw-gateway:\n    image: ${OPENCLAW_IMAGE:-openclaw:local}\n    build: .\n    environment:\n      HOME: \u002Fhome\u002Fnode\n      TERM: xterm-256color\n      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}\n      NODE_ENV: production\n      OPENCLAW_GATEWAY_BIND: ${OPENCLAW_GATEWAY_BIND:-loopback}\n      OPENCLAW_GATEWAY_PORT: ${OPENCLAW_GATEWAY_PORT:-18789}\n      OPENCLAW_SECRET: ${OPENCLAW_SECRET}\n      XDG_CONFIG_HOME: ${XDG_CONFIG_HOME}\n    volumes:\n      - ${OPENCLAW_CONFIG_DIR}:\u002Fhome\u002Fnode\u002F.openclaw\n      - ${OPENCLAW_WORKSPACE_DIR}:\u002Fhome\u002Fnode\u002F.openclaw\u002Fworkspace\n    network_mode: host\n    init: true\n    restart: unless-stopped\n    command:\n      [\"node\", \"dist\u002Findex.js\", \"gateway\",\n       \"--bind\", \"${OPENCLAW_GATEWAY_BIND:-loopback}\",\n       \"--port\", \"18789\"]\n\n  openclaw-cli:\n    image: ${OPENCLAW_IMAGE:-openclaw:local}\n    environment:\n      HOME: \u002Fhome\u002Fnode\n      TERM: xterm-256color\n      OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}\n      BROWSER: echo\n    volumes:\n      - ${OPENCLAW_CONFIG_DIR}:\u002Fhome\u002Fnode\u002F.openclaw\n      - ${OPENCLAW_WORKSPACE_DIR}:\u002Fhome\u002Fnode\u002F.openclaw\u002Fworkspace\n    stdin_open: true\n    tty: true\n    init: true\n    entrypoint: [\"node\", \"dist\u002Findex.js\"]\n","yaml",[53,734,735,744,751,762,772,779,790,801,812,823,834,845,856,867,875,884,892,903,914,925,933,956,969,983,990,998,1007,1014,1023,1032,1041,1052,1059,1066,1073,1083,1093,1102],{"__ignoreMap":63},[67,736,737,741],{"class":69,"line":70},[67,738,740],{"class":739},"s9eBZ","services",[67,742,743],{"class":104},":\n",[67,745,746,749],{"class":69,"line":251},[67,747,748],{"class":739},"  openclaw-gateway",[67,750,743],{"class":104},[67,752,753,756,759],{"class":69,"line":264},[67,754,755],{"class":739},"    image",[67,757,758],{"class":104},": ",[67,760,761],{"class":77},"${OPENCLAW_IMAGE:-openclaw:local}\n",[67,763,764,767,769],{"class":69,"line":280},[67,765,766],{"class":739},"    build",[67,768,758],{"class":104},[67,770,771],{"class":113},".\n",[67,773,774,777],{"class":69,"line":288},[67,775,776],{"class":739},"    environment",[67,778,743],{"class":104},[67,780,782,785,787],{"class":69,"line":781},6,[67,783,784],{"class":739},"      HOME",[67,786,758],{"class":104},[67,788,789],{"class":77},"\u002Fhome\u002Fnode\n",[67,791,793,796,798],{"class":69,"line":792},7,[67,794,795],{"class":739},"      TERM",[67,797,758],{"class":104},[67,799,800],{"class":77},"xterm-256color\n",[67,802,804,807,809],{"class":69,"line":803},8,[67,805,806],{"class":739},"      OPENCLAW_GATEWAY_TOKEN",[67,808,758],{"class":104},[67,810,811],{"class":77},"${OPENCLAW_GATEWAY_TOKEN}\n",[67,813,815,818,820],{"class":69,"line":814},9,[67,816,817],{"class":739},"      NODE_ENV",[67,819,758],{"class":104},[67,821,822],{"class":77},"production\n",[67,824,826,829,831],{"class":69,"line":825},10,[67,827,828],{"class":739},"      OPENCLAW_GATEWAY_BIND",[67,830,758],{"class":104},[67,832,833],{"class":77},"${OPENCLAW_GATEWAY_BIND:-loopback}\n",[67,835,837,840,842],{"class":69,"line":836},11,[67,838,839],{"class":739},"      OPENCLAW_GATEWAY_PORT",[67,841,758],{"class":104},[67,843,844],{"class":77},"${OPENCLAW_GATEWAY_PORT:-18789}\n",[67,846,848,851,853],{"class":69,"line":847},12,[67,849,850],{"class":739},"      OPENCLAW_SECRET",[67,852,758],{"class":104},[67,854,855],{"class":77},"${OPENCLAW_SECRET}\n",[67,857,859,862,864],{"class":69,"line":858},13,[67,860,861],{"class":739},"      XDG_CONFIG_HOME",[67,863,758],{"class":104},[67,865,866],{"class":77},"${XDG_CONFIG_HOME}\n",[67,868,870,873],{"class":69,"line":869},14,[67,871,872],{"class":739},"    volumes",[67,874,743],{"class":104},[67,876,878,881],{"class":69,"line":877},15,[67,879,880],{"class":104},"      - ",[67,882,883],{"class":77},"${OPENCLAW_CONFIG_DIR}:\u002Fhome\u002Fnode\u002F.openclaw\n",[67,885,887,889],{"class":69,"line":886},16,[67,888,880],{"class":104},[67,890,891],{"class":77},"${OPENCLAW_WORKSPACE_DIR}:\u002Fhome\u002Fnode\u002F.openclaw\u002Fworkspace\n",[67,893,895,898,900],{"class":69,"line":894},17,[67,896,897],{"class":739},"    network_mode",[67,899,758],{"class":104},[67,901,902],{"class":77},"host\n",[67,904,906,909,911],{"class":69,"line":905},18,[67,907,908],{"class":739},"    init",[67,910,758],{"class":104},[67,912,913],{"class":113},"true\n",[67,915,917,920,922],{"class":69,"line":916},19,[67,918,919],{"class":739},"    restart",[67,921,758],{"class":104},[67,923,924],{"class":77},"unless-stopped\n",[67,926,928,931],{"class":69,"line":927},20,[67,929,930],{"class":739},"    command",[67,932,743],{"class":104},[67,934,936,939,942,945,948,950,953],{"class":69,"line":935},21,[67,937,938],{"class":104},"      [",[67,940,941],{"class":77},"\"node\"",[67,943,944],{"class":104},", ",[67,946,947],{"class":77},"\"dist\u002Findex.js\"",[67,949,944],{"class":104},[67,951,952],{"class":77},"\"gateway\"",[67,954,955],{"class":104},",\n",[67,957,959,962,964,967],{"class":69,"line":958},22,[67,960,961],{"class":77},"       \"--bind\"",[67,963,944],{"class":104},[67,965,966],{"class":77},"\"${OPENCLAW_GATEWAY_BIND:-loopback}\"",[67,968,955],{"class":104},[67,970,972,975,977,980],{"class":69,"line":971},23,[67,973,974],{"class":77},"       \"--port\"",[67,976,944],{"class":104},[67,978,979],{"class":77},"\"18789\"",[67,981,982],{"class":104},"]\n",[67,984,986],{"class":69,"line":985},24,[67,987,989],{"emptyLinePlaceholder":988},true,"\n",[67,991,993,996],{"class":69,"line":992},25,[67,994,995],{"class":739},"  openclaw-cli",[67,997,743],{"class":104},[67,999,1001,1003,1005],{"class":69,"line":1000},26,[67,1002,755],{"class":739},[67,1004,758],{"class":104},[67,1006,761],{"class":77},[67,1008,1010,1012],{"class":69,"line":1009},27,[67,1011,776],{"class":739},[67,1013,743],{"class":104},[67,1015,1017,1019,1021],{"class":69,"line":1016},28,[67,1018,784],{"class":739},[67,1020,758],{"class":104},[67,1022,789],{"class":77},[67,1024,1026,1028,1030],{"class":69,"line":1025},29,[67,1027,795],{"class":739},[67,1029,758],{"class":104},[67,1031,800],{"class":77},[67,1033,1035,1037,1039],{"class":69,"line":1034},30,[67,1036,806],{"class":739},[67,1038,758],{"class":104},[67,1040,811],{"class":77},[67,1042,1044,1047,1049],{"class":69,"line":1043},31,[67,1045,1046],{"class":739},"      BROWSER",[67,1048,758],{"class":104},[67,1050,1051],{"class":77},"echo\n",[67,1053,1055,1057],{"class":69,"line":1054},32,[67,1056,872],{"class":739},[67,1058,743],{"class":104},[67,1060,1062,1064],{"class":69,"line":1061},33,[67,1063,880],{"class":104},[67,1065,883],{"class":77},[67,1067,1069,1071],{"class":69,"line":1068},34,[67,1070,880],{"class":104},[67,1072,891],{"class":77},[67,1074,1076,1079,1081],{"class":69,"line":1075},35,[67,1077,1078],{"class":739},"    stdin_open",[67,1080,758],{"class":104},[67,1082,913],{"class":113},[67,1084,1086,1089,1091],{"class":69,"line":1085},36,[67,1087,1088],{"class":739},"    tty",[67,1090,758],{"class":104},[67,1092,913],{"class":113},[67,1094,1096,1098,1100],{"class":69,"line":1095},37,[67,1097,908],{"class":739},[67,1099,758],{"class":104},[67,1101,913],{"class":113},[67,1103,1105,1108,1111,1113,1115,1117],{"class":69,"line":1104},38,[67,1106,1107],{"class":739},"    entrypoint",[67,1109,1110],{"class":104},": [",[67,1112,941],{"class":77},[67,1114,944],{"class":104},[67,1116,947],{"class":77},[67,1118,982],{"class":104},[11,1120,1121],{},"The most important line here is probably this one:",[58,1123,1125],{"className":730,"code":1124,"language":732,"meta":63,"style":63},"OPENCLAW_GATEWAY_BIND: ${OPENCLAW_GATEWAY_BIND:-loopback}\n",[53,1126,1127],{"__ignoreMap":63},[67,1128,1129,1132,1134],{"class":69,"line":70},[67,1130,1131],{"class":739},"OPENCLAW_GATEWAY_BIND",[67,1133,758],{"class":104},[67,1135,833],{"class":77},[11,1137,1138,1141,1142,1145],{},[53,1139,1140],{},"loopback"," means the gateway listens on ",[53,1143,1144],{},"127.0.0.1"," only. That is exactly what we want.",[11,1147,1148,1149,1152],{},"Another important change is removing the ",[53,1150,1151],{},"ports:"," section entirely. If you expose the gateway with normal Docker port publishing and then open it in the firewall, you have made the box much more reachable than necessary.",[45,1154,1156],{"id":1155},"modify-the-dockerfile","Modify the Dockerfile",[11,1158,1159,1160,56],{},"There is one small but practical modification I had to make in the ",[53,1161,687],{},[11,1163,1164],{},"Open it:",[58,1166,1168],{"className":60,"code":1167,"language":62,"meta":63,"style":63},"nano Dockerfile\n",[53,1169,1170],{"__ignoreMap":63},[67,1171,1172,1174],{"class":69,"line":70},[67,1173,709],{"class":73},[67,1175,1176],{"class":77}," Dockerfile\n",[11,1178,1179],{},"Near the bottom you will find:",[58,1181,1185],{"className":1182,"code":1183,"language":1184,"meta":63,"style":63},"language-dockerfile shiki shiki-themes github-light github-dark","USER node\n","dockerfile",[53,1186,1187],{"__ignoreMap":63},[67,1188,1189],{"class":69,"line":70},[67,1190,1183],{},[11,1192,1193],{},"Comment that out:",[58,1195,1197],{"className":1182,"code":1196,"language":1184,"meta":63,"style":63},"# USER node\n",[53,1198,1199],{"__ignoreMap":63},[67,1200,1201],{"class":69,"line":70},[67,1202,1196],{},[11,1204,1205],{},"Normally I prefer dropping privileges inside containers, but in this case the agent needs to install tools, manage packages, and write files in ways that quickly run into permissions friction. Leaving that line in place caused more trouble than value for my use case.",[45,1207,1209],{"id":1208},"build-and-run-onboarding","Build and run onboarding",[11,1211,1212],{},"Now build the images:",[58,1214,1216],{"className":60,"code":1215,"language":62,"meta":63,"style":63},"docker compose build\n",[53,1217,1218],{"__ignoreMap":63},[67,1219,1220,1223,1226],{"class":69,"line":70},[67,1221,1222],{"class":73},"docker",[67,1224,1225],{"class":77}," compose",[67,1227,1228],{"class":77}," build\n",[11,1230,1231],{},"Then run the onboarding wizard:",[58,1233,1235],{"className":60,"code":1234,"language":62,"meta":63,"style":63},"docker compose run --rm openclaw-cli onboard\n",[53,1236,1237],{"__ignoreMap":63},[67,1238,1239,1241,1243,1246,1249,1252],{"class":69,"line":70},[67,1240,1222],{"class":73},[67,1242,1225],{"class":77},[67,1244,1245],{"class":77}," run",[67,1247,1248],{"class":113}," --rm",[67,1250,1251],{"class":77}," openclaw-cli",[67,1253,1254],{"class":77}," onboard\n",[11,1256,1257,1258,1261,1262,1265,1266,1269],{},"I went with ",[53,1259,1260],{},"QuickStart",", then ",[53,1263,1264],{},"OpenAI (Codex OAuth + API key)",", followed by ",[53,1267,1268],{},"OpenAI Codex (ChatGPT OAuth)",", but you can pick whichever provider works for you.",[11,1271,1272],{},"The OAuth flow is slightly awkward the first time, but it is simple enough:",[680,1274,1275,1278,1281,1284,1290,1293],{},[29,1276,1277],{},"The wizard prints a URL.",[29,1279,1280],{},"Open it in your laptop browser.",[29,1282,1283],{},"Log in and approve.",[29,1285,1286,1287],{},"The browser redirects to something like ",[53,1288,1289],{},"http:\u002F\u002Flocalhost:xxxx\u002Fauth\u002Fcallback?code=...",[29,1291,1292],{},"The page may say it cannot connect. That is normal.",[29,1294,1295],{},"Copy the entire redirected URL and paste it back into the terminal.",[11,1297,1298],{},"If you want Telegram, configure it during onboarding. I skipped the optional skills, hooks, and extra API key prompts for the initial setup.",[45,1300,1302],{"id":1301},"fix-the-bind-mode-if-the-wizard-changes-it","Fix the bind mode if the wizard changes it",[11,1304,1305],{},"After onboarding, verify that the gateway bind value is still correct:",[58,1307,1309],{"className":60,"code":1308,"language":62,"meta":63,"style":63},"grep '\"bind\"' \u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fopenclaw.json\n",[53,1310,1311],{"__ignoreMap":63},[67,1312,1313,1316,1319],{"class":69,"line":70},[67,1314,1315],{"class":73},"grep",[67,1317,1318],{"class":77}," '\"bind\"'",[67,1320,1321],{"class":77}," \u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fopenclaw.json\n",[11,1323,1324],{},"It should say:",[58,1326,1330],{"className":1327,"code":1328,"language":1329,"meta":63,"style":63},"language-json shiki shiki-themes github-light github-dark","\"bind\": \"loopback\"\n","json",[53,1331,1332],{"__ignoreMap":63},[67,1333,1334,1337,1339],{"class":69,"line":70},[67,1335,1336],{"class":77},"\"bind\"",[67,1338,758],{"class":104},[67,1340,1341],{"class":77},"\"loopback\"\n",[11,1343,1344,1345,1348],{},"If it says ",[53,1346,1347],{},"lan",", change it. This is not a cosmetic setting. It controls whether the gateway stays local or starts listening more broadly than intended.",[45,1350,1352],{"id":1351},"fix-the-gateway-token-mismatch","Fix the gateway token mismatch",[11,1354,1355],{},"This was the part that felt most likely to waste time later if left unnoticed.",[11,1357,1358,1359,1362,1363,1365],{},"The onboarding wizard can write its own gateway token into ",[53,1360,1361],{},"openclaw.json",", and that token may not match the one in your ",[53,1364,646],{},". If the tokens differ, you will get weird failures later: CLI commands fail, subagents do not spawn correctly, cron jobs misbehave, and the setup feels broken in ways that are harder to reason about than they should be.",[11,1367,1368],{},"Check the token written by the wizard:",[58,1370,1372],{"className":60,"code":1371,"language":62,"meta":63,"style":63},"python3 -c \"import json; c=json.load(open('\u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fopenclaw.json')); print(c['gateway']['auth']['token'])\"\n",[53,1373,1374],{"__ignoreMap":63},[67,1375,1376,1379,1382],{"class":69,"line":70},[67,1377,1378],{"class":73},"python3",[67,1380,1381],{"class":113}," -c",[67,1383,1384],{"class":77}," \"import json; c=json.load(open('\u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fopenclaw.json')); print(c['gateway']['auth']['token'])\"\n",[11,1386,1387,1388,1390],{},"Then compare it with the token in ",[53,1389,646],{},":",[58,1392,1394],{"className":60,"code":1393,"language":62,"meta":63,"style":63},"grep OPENCLAW_GATEWAY_TOKEN \u002Fhome\u002Fopenclaw\u002Fopenclaw\u002F.env\n",[53,1395,1396],{"__ignoreMap":63},[67,1397,1398,1400,1403],{"class":69,"line":70},[67,1399,1315],{"class":73},[67,1401,1402],{"class":77}," OPENCLAW_GATEWAY_TOKEN",[67,1404,1405],{"class":77}," \u002Fhome\u002Fopenclaw\u002Fopenclaw\u002F.env\n",[11,1407,1408],{},"If they are different, edit the JSON file and make them match:",[58,1410,1412],{"className":60,"code":1411,"language":62,"meta":63,"style":63},"nano \u002Fhome\u002Fopenclaw\u002F.openclaw\u002Fopenclaw.json\n",[53,1413,1414],{"__ignoreMap":63},[67,1415,1416,1418],{"class":69,"line":70},[67,1417,709],{"class":73},[67,1419,1321],{"class":77},[45,1421,1423,1424,1426],{"id":1422},"the-final-openclawjson-gateway-shape","The final ",[53,1425,1361],{}," gateway shape",[11,1428,1429,1430,1432],{},"The gateway section in ",[53,1431,1361],{}," should look like this:",[58,1434,1436],{"className":1327,"code":1435,"language":1329,"meta":63,"style":63},"\"gateway\": {\n  \"port\": 18789,\n  \"mode\": \"local\",\n  \"bind\": \"loopback\",\n  \"auth\": {\n    \"mode\": \"token\",\n    \"token\": \"YOUR_TOKEN_FROM_ENV_FILE\",\n    \"rateLimit\": {\n      \"maxAttempts\": 10,\n      \"windowMs\": 60000,\n      \"lockoutMs\": 300000\n    }\n  },\n  \"controlUi\": {\n    \"enabled\": true,\n    \"allowInsecureAuth\": false\n  }\n}\n",[53,1437,1438,1445,1457,1469,1481,1488,1500,1512,1519,1531,1543,1553,1558,1563,1570,1582,1592,1597],{"__ignoreMap":63},[67,1439,1440,1442],{"class":69,"line":70},[67,1441,952],{"class":77},[67,1443,1444],{"class":104},": {\n",[67,1446,1447,1450,1452,1455],{"class":69,"line":251},[67,1448,1449],{"class":113},"  \"port\"",[67,1451,758],{"class":104},[67,1453,1454],{"class":113},"18789",[67,1456,955],{"class":104},[67,1458,1459,1462,1464,1467],{"class":69,"line":264},[67,1460,1461],{"class":113},"  \"mode\"",[67,1463,758],{"class":104},[67,1465,1466],{"class":77},"\"local\"",[67,1468,955],{"class":104},[67,1470,1471,1474,1476,1479],{"class":69,"line":280},[67,1472,1473],{"class":113},"  \"bind\"",[67,1475,758],{"class":104},[67,1477,1478],{"class":77},"\"loopback\"",[67,1480,955],{"class":104},[67,1482,1483,1486],{"class":69,"line":288},[67,1484,1485],{"class":113},"  \"auth\"",[67,1487,1444],{"class":104},[67,1489,1490,1493,1495,1498],{"class":69,"line":781},[67,1491,1492],{"class":113},"    \"mode\"",[67,1494,758],{"class":104},[67,1496,1497],{"class":77},"\"token\"",[67,1499,955],{"class":104},[67,1501,1502,1505,1507,1510],{"class":69,"line":792},[67,1503,1504],{"class":113},"    \"token\"",[67,1506,758],{"class":104},[67,1508,1509],{"class":77},"\"YOUR_TOKEN_FROM_ENV_FILE\"",[67,1511,955],{"class":104},[67,1513,1514,1517],{"class":69,"line":803},[67,1515,1516],{"class":113},"    \"rateLimit\"",[67,1518,1444],{"class":104},[67,1520,1521,1524,1526,1529],{"class":69,"line":814},[67,1522,1523],{"class":113},"      \"maxAttempts\"",[67,1525,758],{"class":104},[67,1527,1528],{"class":113},"10",[67,1530,955],{"class":104},[67,1532,1533,1536,1538,1541],{"class":69,"line":825},[67,1534,1535],{"class":113},"      \"windowMs\"",[67,1537,758],{"class":104},[67,1539,1540],{"class":113},"60000",[67,1542,955],{"class":104},[67,1544,1545,1548,1550],{"class":69,"line":836},[67,1546,1547],{"class":113},"      \"lockoutMs\"",[67,1549,758],{"class":104},[67,1551,1552],{"class":113},"300000\n",[67,1554,1555],{"class":69,"line":847},[67,1556,1557],{"class":104},"    }\n",[67,1559,1560],{"class":69,"line":858},[67,1561,1562],{"class":104},"  },\n",[67,1564,1565,1568],{"class":69,"line":869},[67,1566,1567],{"class":113},"  \"controlUi\"",[67,1569,1444],{"class":104},[67,1571,1572,1575,1577,1580],{"class":69,"line":877},[67,1573,1574],{"class":113},"    \"enabled\"",[67,1576,758],{"class":104},[67,1578,1579],{"class":113},"true",[67,1581,955],{"class":104},[67,1583,1584,1587,1589],{"class":69,"line":886},[67,1585,1586],{"class":113},"    \"allowInsecureAuth\"",[67,1588,758],{"class":104},[67,1590,1591],{"class":113},"false\n",[67,1593,1594],{"class":69,"line":894},[67,1595,1596],{"class":104},"  }\n",[67,1598,1599],{"class":69,"line":905},[67,1600,1601],{"class":104},"}\n",[11,1603,1604],{},"There are three fields here that matter a lot:",[26,1606,1607,1615,1621],{},[29,1608,1609,1612,1613],{},[53,1610,1611],{},"bind: \"loopback\""," keeps the gateway local to ",[53,1614,1144],{},[29,1616,1617,1620],{},[53,1618,1619],{},"allowInsecureAuth: false"," ensures you do not weaken device pairing",[29,1622,1623,1626],{},[53,1624,1625],{},"rateLimit"," gives you a basic brute-force safety net",[11,1628,1629],{},"This is the part where the setup stops being “just a local tool running in Docker” and starts becoming a service you can operate with a bit more confidence.",[45,1631,1633],{"id":1632},"final-thoughts","Final thoughts",[11,1635,1636],{},"OpenClaw is not particularly hard to get running. The hard part is resisting the temptation to stop the moment you see the happy path working.",[11,1638,1639],{},"A secure setup here is mostly about discipline:",[26,1641,1642,1645,1648,1651,1654,1657],{},[29,1643,1644],{},"do the firewall first",[29,1646,1647],{},"move off root",[29,1649,1650],{},"keep the gateway on loopback",[29,1652,1653],{},"do not open the port publicly",[29,1655,1656],{},"keep persistent data outside the container",[29,1658,1659],{},"make sure the tokens are actually in sync",[11,1661,1662],{},"If you skip those parts, the setup is technically “done”, but not in a way I would feel good about leaving on a VPS.",[11,1664,1665],{},"My general rule is simple: whenever a tool can execute commands, install packages, and read or write files, the surrounding environment matters as much as the tool itself.",[11,1667,1668],{},"That is exactly the kind of software that deserves a hardened setup from day one.",[1670,1671,1672],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":63,"searchDepth":251,"depth":251,"links":1674},[1675,1676,1677,1678,1679,1680,1681,1682,1683,1684,1685,1687],{"id":47,"depth":251,"text":48},{"id":218,"depth":251,"text":219},{"id":322,"depth":251,"text":323},{"id":542,"depth":251,"text":543},{"id":639,"depth":251,"text":640},{"id":667,"depth":251,"text":668},{"id":1155,"depth":251,"text":1156},{"id":1208,"depth":251,"text":1209},{"id":1301,"depth":251,"text":1302},{"id":1351,"depth":251,"text":1352},{"id":1422,"depth":251,"text":1686},"The final openclaw.json gateway shape",{"id":1632,"depth":251,"text":1633},"AI","2026\u002F03\u002F23",null,"md","\u002Fimages\u002Fopenclaw.png","openclaw, vps, docker, ubuntu, secure openclaw setup, hardened server setup, ssh hardening, ufw, fail2ban",{},"\u002Fblog\u002Fopen-claw-secure-hardened-setup","☕️☕️ 12 min read",{"title":6,"description":13},"open-claw-secure-hardened-setup","blog\u002Fopen-claw-secure-hardened-setup","ai, devops, docker, security, vps","1ndE6Pq3cRoQjtThOp6cmCWCXXUcKnBO70YryaJNr70",{"id":1703,"title":1704,"body":1705,"category":2336,"date":2337,"description":2338,"excerpt":1690,"extension":1691,"image":2339,"keywords":2340,"meta":2341,"navigation":988,"path":2342,"readingTime":2343,"seo":2344,"slug":2345,"stem":2346,"tags":2347,"__hash__":2348},"blog\u002Fblog\u002Fcosmos-sdk-overview-through-the-lens-of-a-javascript-developer.md","Cosmos SDK overview through the lens of a JavaScript Developer",{"type":8,"value":1706,"toc":2323},[1707,1718,1742,1746,1757,1762,1772,1775,1795,1799,1806,1813,1835,1841,1847,1855,1861,1906,1912,1944,1947,1951,1954,1957,2009,2013,2027,2030,2033,2036,2077,2080,2085,2088,2142,2147,2161,2164,2181,2185,2188,2196,2200,2223,2262,2265,2268],[11,1708,1709,1710,1717],{},"Why does a JavaScript developer write about a framework for building application-specific blockchains? We at ",[1711,1712,1716],"a",{"href":1713,"rel":1714},"http:\u002F\u002Finjectiveprotocol.com\u002F",[1715],"nofollow","Injective Protocol"," are building our own Injective Chain on the Cosmos SDK — that alone was enough reason for me to dig into it.",[11,1719,1720,1721,1724,1725,1730,1731,1736,1737,1741],{},"So, what is the Cosmos SDK? Their official docs state that ",[21,1722,1723],{},"Cosmos SDK is the world’s most popular framework for building application-specific blockchains."," It’s written in ",[1711,1726,1729],{"href":1727,"rel":1728},"https:\u002F\u002Fgolang.org\u002F",[1715],"Go"," programming language and uses ",[1711,1732,1735],{"href":1733,"rel":1734},"https:\u002F\u002Ftendermint.com\u002F",[1715],"Tendermint"," core as a consensus algorithm. Tendermint is responsible for the networking and the consensus layer, and Cosmos SDK handles the business logic. The Cosmos SDK and Tendermint communicate using the ABCI (",[1738,1739,1740],"em",{},"Application Blockchain Interface","). Let’s get into the specifics.",[45,1743,1745],{"id":1744},"tendermint-core","Tendermint Core",[11,1747,1748,1749,1752,1753,1756],{},"Tendermint Core is a blockchain application platform; it provides the equivalent of a web-server, database, and supporting libraries for blockchain applications written in any programming language. Tendermint Core is the leading BFT (Byzantine Fault Tolerance) engine for building blockchains. The safety threshold is ",[53,1750,1751],{},"1\u002F3"," of the validator's power. Every block that is produced is considered final (i.e blocks have ",[21,1754,1755],{},"instant finality","). Also, with the ABCI interface, Tendermint Core supports state machines written in any programming language.",[1758,1759,1761],"h3",{"id":1760},"tendermint-bft","Tendermint BFT",[11,1763,1764,1765,1768,1769,56],{},"Byzantine Fault Tolerance is the feature of a distributed network to reach consensus ",[1738,1766,1767],{},"(agreement on the same value)"," even when some of the nodes in the network fail to respond or respond with incorrect information. The objective of a BFT mechanism is to safeguard against system failures by employing collective decision-making (both – correct and faulty nodes) which aims to reduce to influence of the faulty nodes. BFT is derived from ",[1738,1770,1771],{},"Byzantine Generals’ Problem",[11,1773,1774],{},"The advantages of BFT are:",[26,1776,1777,1783,1789],{},[29,1778,1779,1782],{},[21,1780,1781],{},"Energy efficiency -"," It can achieve distributed consensus without carrying out complex mathematical computations (like in Proof of Work).",[29,1784,1785,1788],{},[21,1786,1787],{},"Transaction finality",": The transactions do not require multiple confirmations (like in the case of the PoW mechanism in Bitcoin where every node individually verifies all the transactions before adding the new block to the blockchain; confirmations can take between 10-60 minutes depending upon how many entities confirm the new block) after they have been finalized and agreed upon.",[29,1790,1791,1794],{},[21,1792,1793],{},"Low reward variance",": Every node in the network takes part in responding to the request by the client and hence every node can be incentivized leading to low variance in rewarding the nodes that help in decision making.",[1758,1796,1798],{"id":1797},"bft-consensus-algorithm","BFT Consensus Algorithm",[11,1800,1801,1802,1805],{},"The participants in the protocol are called ",[21,1803,1804],{},"validators",", who take turns in proposing blocks of transactions and vote on them. These turns are assigned to the validators using round-robin scheduling according to their total amount of stake.",[11,1807,1808,1809,1812],{},"Another key participant in the protocol are the ",[21,1810,1811],{},"delegators",". Delegators are token holders that do not want to run validator operations (ie. invest in the necessary equipment to be able to participate in the consensus protocol) and, as the name suggests, they delegate their stake tokens to validators, who charge a commission on the corresponding fees obtained by the delegated tokens.",[11,1814,1815,1816,1819,1820,1823,1824,1827,1828,1831,1832,56],{},"Tendermint BFT works using three steps: ",[21,1817,1818],{},"proposing",", ",[21,1821,1822],{},"pre-voting"," and ",[21,1825,1826],{},"pre-committing."," The sequence ",[53,1829,1830],{},"(Propose -> Pre-vote -> Pre-commit)"," is called ",[21,1833,1834],{},"round",[11,1836,1837],{},[1838,1839],"img",{"alt":1798,"src":1840},"\u002Fimages\u002Fposts\u002Fbft-consensus-algorithm.png",[11,1842,1843,1844],{},"1) ",[21,1845,1846],{},"Propose step",[26,1848,1849,1852],{},[29,1850,1851],{},"A validator node from the validator set is chosen as block proposer for a given height.",[29,1853,1854],{},"The validator picks up transactions and packs them in a block. The proposed block is broadcasted to the rest of nodes.",[11,1856,1857,1858],{},"2) ",[21,1859,1860],{},"Pre-Vote step",[26,1862,1863],{},[29,1864,1865,1866,1871,1872,1875,1876,1879,1880,1883,1884,1889,1890,1892,1893,1898,1899,1902,1903,56],{},"Each node casts a ",[1738,1867,1868],{},[21,1869,1870],{},"pre-vote"," and listens until ",[53,1873,1874],{},"+2\u002F3"," pre-votes from validators' nodes have been submitted. A block can be either pre-voted as ",[1738,1877,1878],{},"a pre-vote"," (ie. valid block) or ",[53,1881,1882],{},"nil"," (when it's invalid or timeout reached). We generally call a ",[1738,1885,1886],{},[21,1887,1888],{},"Polka"," when ",[53,1891,1874],{}," of validator pre-vote for the same block. Also, in the perspective of a validator, if the validator voted for the block that is referred to in the Polka, the validator now has what's called a ",[1738,1894,1895],{},[21,1896,1897],{},"proof-of-lock-change"," or ",[21,1900,1901],{},"PoLC"," for short in that particular height and round ",[53,1904,1905],{},"(H, R)",[11,1907,1908,1909],{},"3) ",[21,1910,1911],{},"Pre-Commit step",[26,1913,1914,1932],{},[29,1915,1916,1917,1922,1923,1925,1926,1928,1929,1931],{},"Once a Polka is reached, validators submit a ",[1738,1918,1919],{},[21,1920,1921],{},"pre-commit"," block, otherwise they ",[1738,1924,1921],{}," ",[53,1927,1882],{},". After they are broadcasted, they wait for ",[53,1930,1874],{}," pre-commits from their peers.",[29,1933,1934,1935,1937,1938,1943],{},"Finally, the proposed block is committed when validators submit more than ",[53,1936,1874],{}," pre-commits (aka ",[1738,1939,1940],{},[21,1941,1942],{},"Commit",") and a new block height is reached with a new selected block proposer. If not, the network performs a new round and the process starts from the beginning (1).",[11,1945,1946],{},"One important thing to consider is that these pre-votes are included in the next block as proof that the previous block was committed - they cannot be included in the current block, as that block has already been created.",[1758,1948,1950],{"id":1949},"abci-application-blockchain-interface","ABCI (Application BlockChain Interface)",[11,1952,1953],{},"The communication between the Cosmos SDK and Tendermint goes through the ABCI interface. Tendermint only handles transaction bytes. It has no knowledge of what these bytes mean. All Tendermint does is order these transaction bytes deterministically. Tendermint passes the bytes to the application via the ABCI, and expects a return code to inform it if the messages contained in the transactions were successfully processed or not.",[11,1955,1956],{},"Here are the most important messages of the ABCI:",[26,1958,1959,1967,1975,1987],{},[29,1960,1961,1966],{},[21,1962,1963,1390],{},[53,1964,1965],{},"CheckTx"," Validate transaction. Mempool only relays valid transactions to its peers.",[29,1968,1969,1974],{},[21,1970,1971,1390],{},[53,1972,1973],{},"DeliverTx"," Deliver transaction. Validate tx against the current state, application protocol and the cryptographic credentials.",[29,1976,1977,1981,1982,1986],{},[21,1978,1979,1390],{},[53,1980,1942],{}," Persist app state to disk. Transactions are now considered final. Remain transactions in the mempool are replayed (",[21,1983,1984],{},[53,1985,1965],{},").",[29,1988,1989,1994,1995],{},[21,1990,1991,1390],{},[53,1992,1993],{},"BeginBlock\u002FEndBlock"," Run code at the beginning\u002Fend of every block. ",[21,1996,1997,1998,2000,2001,2004,2005,2008],{},"Every application module can define its own ",[53,1999,1993],{}," functions in their ",[53,2002,2003],{},"BeginBlocker"," and ",[53,2006,2007],{},"EndBlocker"," function within the module.",[45,2010,2012],{"id":2011},"cosmos-sdk","Cosmos SDK",[11,2014,2015,2016,2019,2020,2023,2024],{},"The Cosmos-SDK is an open-source framework for building multi-asset public ",[21,2017,2018],{},"Proof-of-Stake"," (PoS) blockchains, as well as permission ",[21,2021,2022],{},"Proof-Of-Authority"," (PoA) blockchains. Blockchains built with the Cosmos SDK are generally referred to as ",[21,2025,2026],{},"application-specific blockchains.",[11,2028,2029],{},"The goal of the Cosmos SDK is to allow developers to easily create custom blockchains from scratch that can natively interoperate with other blockchains. SDK-based blockchains are built out of composable modules, most of which are open source and readily available for any developers to use. Anyone can create a module for the Cosmos-SDK, and integrating already-built modules is as simple as importing them into your blockchain application.",[11,2031,2032],{},"The Cosmos SDK is a framework that facilitates the development of secure state machines on top of Tendermint. At its core, the SDK is a boilerplate implementation of the ABCI in Go. It comes with a multistore to persist data and a router to handle transactions.",[11,2034,2035],{},"The main components of a Cosmos SDK app are:",[26,2037,2038,2046,2054,2060,2071],{},[29,2039,2040,2045],{},[21,2041,2042],{},[53,2043,2044],{},"Baseapp",": Boilerplate implementation of a Cosmos SDK application. It comes with an implementation of the ABCI to handle the connection with the underlying consensus engine.",[29,2047,2048,2053],{},[21,2049,2050],{},[53,2051,2052],{},"Multistore",": The Cosmos SDK provides a multistore for persisting state. It's a store of key-value stores (KVStores).",[29,2055,2056,2059],{},[21,2057,2058],{},"App Codec",": encoding of state and client logic (Amino or Protobuf)",[29,2061,2062,2065,2066],{},[21,2063,2064],{},"Module Keepers",": hold a key to access the module's KVStore. Secured by ",[1711,2067,2070],{"href":2068,"rel":2069},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject-capability_model",[1715],"object-capability model.",[29,2072,2073,2076],{},[21,2074,2075],{},"Module manager",": List of the app modules which contain the application business logic.",[11,2078,2079],{},"Let's dig deeper into these components.",[1758,2081,2083],{"id":2082},"baseapp",[53,2084,2044],{},[11,2086,2087],{},"Boilerplate implementation of a Cosmos SDK application. It comes with an implementation of the ABCI to handle the connection with the underlying consensus engine.",[26,2089,2090,2107,2117,2126,2131],{},[29,2091,2092,758,2095,944,2097,944,2099,944,2101,944,2104],{},[21,2093,2094],{},"Tendermint ABCI",[53,2096,1965],{},[53,2098,1973],{},[53,2100,1942],{},[53,2102,2103],{},"Begin\u002FEnd Block",[53,2105,2106],{},"Info",[29,2108,2109,2112,2113,2116],{},[21,2110,2111],{},"Transaction\u002FQuery Router",": runs each ",[53,2114,2115],{},"Msg"," through the Router\u002FQuery handler for each of the modules",[29,2118,2119,2122,2123],{},[21,2120,2121],{},"Tendermint consensus parameters",": Block, Evidence, and Validators ",[1738,2124,2125],{},"(can be changed through governance)",[29,2127,2128],{},[21,2129,2130],{},"DB and Logger",[29,2132,2133,2138,2139],{},[21,2134,2135],{},[53,2136,2137],{},"AnteHandler",": special logic that is executed before the actual transaction execution happens ",[1738,2140,2141],{},"(eg: balance, signature verification, gas consumption, etc).",[1758,2143,2145],{"id":2144},"multistore",[53,2146,2052],{},[11,2148,2149,2150,2157,2158,2160],{},"Store of key-value stores (KVStore). Each module contains a key to one or more KVStore. It supports different databases ",[1738,2151,2152,2153,2156],{},"(GolevelDB ",[67,2154,2155],{},"default",", RocksDB, BoltDB, etc)",". App state persisted to disk during ABCI ",[53,2159,1942],{}," phase.",[11,2162,2163],{},"There are different types of stores:",[26,2165,2166,2172],{},[29,2167,2168,2171],{},[21,2169,2170],{},"IAVL Store",": self-balancing binary tree. Get and Set operations are O(log n).",[29,2173,2174,2177,2178,56],{},[21,2175,2176],{},"Transcient Store",": automatically discarded at the end of the block. Useful to persist information that is only relevant per-block ",[1738,2179,2180],{},"(eg: parameter changes)",[1758,2182,2184],{"id":2183},"keepers","Keepers",[11,2186,2187],{},"Gatekeepers for a given KVStore. They are usually associated with a single module. They hold a reference to a store key and a codec for de\u002Fencoding of types from\u002Fto the store.",[11,2189,2190,2191,56],{},"They can also hold a reference to other modules' ",[1711,2192,2195],{"href":2193,"rel":2194},"https:\u002F\u002Fdocs.cosmos.network\u002Fv0.42\u002Fbuilding-modules\u002Fkeeper.html#type-definition",[1715],"expected Keepers",[1758,2197,2199],{"id":2198},"module-manager","Module Manager",[11,2201,2202,2203,2206,2207,2210,2211,2214,2215,2218,2219,2222],{},"The center piece of the Cosmos SDK. The modules are a way to implement the business logic of the Cosmos Application. Usually located in the ",[53,2204,2205],{},"x"," folder ",[1738,2208,2209],{},"(stands for extensions)",". When designing and building a module, Composability ",[1738,2212,2213],{},"(can be reused)",", Specialisation ",[1738,2216,2217],{},"(manages a specific set of business logic)"," and Capabilities ",[1738,2220,2221],{},"(each module has a single reference to a KVStore, and overall security measures for the module, what it can and can't do)"," should be the key considerations.",[26,2224,2225,2231,2234,2237,2246,2259],{},[29,2226,2227,2228,56],{},"Manages a collection of ",[53,2229,2230],{},"AppModules",[29,2232,2233],{},"It also defines a common interface that modules need to expose",[29,2235,2236],{},"Register clients",[29,2238,2239,2240,2004,2243],{},"Routes for tx and queries; ",[53,2241,2242],{},"Handler",[53,2244,2245],{},"Query",[29,2247,2248,2249,944,2252,944,2255,2258],{},"ABCI Logic (",[53,2250,2251],{},"InitChain",[53,2253,2254],{},"Import\u002FExportGenesis",[53,2256,2257],{},"Begin\u002FEndBlock",")",[29,2260,2261],{},"Module invariants and simulation registration",[11,2263,2264],{},"That's about it. The next post will cover Cosmos SDK modules and then IBC. I'll continue writing on this topic as I keep exploring. ✌️",[11,2266,2267],{},"References:",[26,2269,2270,2275,2280,2288,2296,2304,2310,2317],{},[29,2271,2272],{},[1711,2273,1729],{"href":1727,"rel":2274},[1715],[29,2276,2277],{},[1711,2278,1735],{"href":1733,"rel":2279},[1715],[29,2281,2282,2287],{},[1711,2283,2286],{"href":2284,"rel":2285},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FByzantine_fault",[1715],"BFT"," (Byzantine Fault Tolerance)",[29,2289,2290,2295],{},[1711,2291,2294],{"href":2292,"rel":2293},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProof_of_stake",[1715],"PoS"," (Proof of Stake)",[29,2297,2298,2303],{},[1711,2299,2302],{"href":2300,"rel":2301},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRound-robin_scheduling",[1715],"RRA"," (Round Robin Algorithm)",[29,2305,2306],{},[1711,2307,2012],{"href":2308,"rel":2309},"https:\u002F\u002Fdocs.cosmos.network\u002Fv0.42\u002F",[1715],[29,2311,2312],{},[1711,2313,2316],{"href":2314,"rel":2315},"https:\u002F\u002Fcosmos-network.gitbooks.io\u002Fcosmos-academy\u002Fcontent\u002Fintroduction-to-the-cosmos-ecosystem\u002Ftendermint-bft-consensus-algorithm.html",[1715],"Tendermint BFT Consensus Algorithm",[29,2318,2319],{},[1711,2320,2322],{"href":2068,"rel":2321},[1715],"Object-Capability Model",{"title":63,"searchDepth":251,"depth":251,"links":2324},[2325,2330],{"id":1744,"depth":251,"text":1745,"children":2326},[2327,2328,2329],{"id":1760,"depth":264,"text":1761},{"id":1797,"depth":264,"text":1798},{"id":1949,"depth":264,"text":1950},{"id":2011,"depth":251,"text":2012,"children":2331},[2332,2333,2334,2335],{"id":2082,"depth":264,"text":2044},{"id":2144,"depth":264,"text":2052},{"id":2183,"depth":264,"text":2184},{"id":2198,"depth":264,"text":2199},"Blockchain","2021\u002F04\u002F10","Why does a JavaScript developer write about a framework for building application-specific blockchains? We at Injective Protocol are building our own Injective Chain on the Cosmos SDK — that alone was enough reason for me to dig into it.","\u002Fimages\u002Fcosmos-sdk.jpeg","cosmos, cosmos-sdk, blockchain, tendermint, bft, byzantine fault tolerance",{},"\u002Fblog\u002Fcosmos-sdk-overview-through-the-lens-of-a-javascript-developer","☕️ 10 min read",{"title":1704,"description":2338},"cosmos-sdk-overview-through-the-lens-of-a-javascript-developer","blog\u002Fcosmos-sdk-overview-through-the-lens-of-a-javascript-developer","cosmos, blockchain","AiqUZZDJCHuLCEC3LHk7631vT0nuAoesuxndELCv_lA",{"id":2350,"title":2351,"body":2352,"category":6223,"date":6224,"description":6225,"excerpt":1690,"extension":1691,"image":6226,"keywords":6227,"meta":6228,"navigation":988,"path":6229,"readingTime":6230,"seo":6231,"slug":6232,"stem":6233,"tags":6234,"__hash__":6235},"blog\u002Fblog\u002Funderstand-and-use-solid-design-principles-with-typescript.md","Understand and use SOLID Design Principles with TypeScript",{"type":8,"value":2353,"toc":6212},[2354,2361,2365,2370,2373,2377,2383,2390,2393,2397,2408,2414,2446,2449,2453,2459,2462,2757,2764,2767,3277,3283,3304,3307,3312,3334,3338,3341,3347,3366,3477,3502,3869,3888,3895,3909,4323,4328,4347,4351,4367,4370,4375,4378,4712,4735,4738,5019,5023,5030,5033,5191,5211,5387,5404,5408,5415,5418,5421,5424,5432,5443,5711,5726,5733,6110,6123,6200,6203,6206,6209],[11,2355,2356,2357,2360],{},"Yes, another take into explaining SOLID Design Principles. And yes, another blog post about making your code more maintainable and readable. But this time something is different - well, mostly nothing really is (",[1738,2358,2359],{},"you expected something here, didn't ya?",") except that it's just my perspective. A perspective gained from applying these principles in real life projects.",[45,2362,2364],{"id":2363},"what-is-a-design-principle","What is a Design Principle?",[11,2366,2367],{},[1738,2368,2369],{},"Software design principles are concerned with providing means to handle the complexity of the design process effectively. Effectively managing the complexity will not only reduce the effort needed for design but can also reduce the scope of introducing errors during design. Design Principles are standards used to organize and arrange the structural components of Software Engineering design. Methods in which these design principles are applied affect the expressive content and the working process from the start. Design principles help developers build-up common consensus about architectural knowledge, help people process with large-scale software engineering, help beginners avoid traps and pitfalls which have been detected by past experiences.",[11,2371,2372],{},"Reading the above definition might scare you if you are a beginner or confuse you if you are a seasoned developer. To explain in a concise and understandable way - the goal of a design principle is to make our software easier to understand, read, extend and maintain.",[45,2374,2376],{"id":2375},"why-do-we-need-design-principles","Why do we need Design Principles",[11,2378,2379,2380,56],{},"Imagine you are put into a position to work on a legacy codebase that didn't have proper code architecture and components within the projects are coupled together and you need to either fix some minor bug or extend the code with some functionality. You will find yourself jumping back and forth between individual components to understand how these individual components are coupled. Once you think you understood how the system is structured and how do these components behave (and are interconnected between each other), you start extending the codebase. But then again, while extending some of the things might not seem logical at first, so you have to adapt your mindset to follow the existing codebase, which will again force to you ",[21,2381,2382],{},"spend more time reading than writing code",[11,2384,2385,2386,2389],{},"These issues don't arise only on legacy codebases. Let's say you are working for a startup (or start one of your own), and you are in charge of a project. You start your project from a clean slate and you are in charge of its architecture and the whole development cycle. We all know that startups want to release new features and new products as fast as possible, so you end up spending less time thinking about the code structure and more about just building up new features. And this is okay if you have a ",[1738,2387,2388],{},"short-term"," vision for your startup. But what happens if your startup gets enough steam and is in it for the long run? How much technical debt have you introduced and how much time and money is it going to cost you while trying to scale and maintain your codebase?",[11,2391,2392],{},"Implementing these design principles is not something you will waste extra time on. Once you understand their meaning and their usage, gain a little bit of real-life experience, they will be part of your development process - making you a better and more-seniorish-like developer.",[45,2394,2396],{"id":2395},"what-does-solid-stand-for","What does SOLID stand for",[11,2398,2399,2402,2403,56],{},[21,2400,2401],{},"SOLID"," is an acronym for the first five object-oriented design (OOD) principles by Robert C. Martin, popularly known as ",[1711,2404,2407],{"href":2405,"rel":2406},"https:\u002F\u002Ftwitter.com\u002FUncleBob",[1715],"@UncleBob",[11,2409,2410,2411,2413],{},"The following 5 concepts make up our ",[21,2412,2401],{}," principles:",[680,2415,2416,2422,2428,2434,2440],{},[29,2417,2418,2421],{},[21,2419,2420],{},"S","ingle Responsibility",[29,2423,2424,2427],{},[21,2425,2426],{},"O","pen\u002FClosed",[29,2429,2430,2433],{},[21,2431,2432],{},"L","iskov Substitution",[29,2435,2436,2439],{},[21,2437,2438],{},"I","nterface Segregation",[29,2441,2442,2445],{},[21,2443,2444],{},"D","ependency Inversion",[11,2447,2448],{},"While some of these words may sound daunting, they can be easily understood with some simple code examples. In the following sections, we'll take a deep dive into what each of these principles means, along with a quick TypeScript example to illustrate each one.",[1758,2450,2452],{"id":2451},"single-responsibility-principle-srp","Single Responsibility Principle (SRP)",[11,2454,2455,2458],{},[21,2456,2457],{},"The SRP requires that a class should have only one reason to change."," A class that follows this principle performs just a few related tasks. You don’t need to limit your thinking to classes when considering the SRP. You can apply the principle to methods or modules, ensuring that they do just one thing and therefore have just one reason to change.",[11,2460,2461],{},"Let's look at an example where we violate SRP:",[58,2463,2467],{"className":2464,"code":2465,"language":2466,"meta":63,"style":63},"language-ts shiki shiki-themes github-light github-dark","import axios from \"axios\";\n\nclass User {\n  private emailServiceUrl = `emailService.example\u002Fapi`;\n  private email: string;\n  private name: string;\n\n  constructor(name: string, email: string) {\n    this.name = name;\n    this.email = email;\n  }\n\n  sendGreetingEmail() {\n    const { name, email, emailServiceUrl } = this;\n    const body = `Hey ${name}. Welcome to our app!`;\n\n    return axios(emailServiceUrl, {\n      data: {\n        email,\n        body,\n      },\n    });\n  }\n}\n\nconst user = new User(\"John\", \"email@example.com\");\nuser.sendGreetingEmail();\n","ts",[53,2468,2469,2486,2490,2501,2518,2532,2545,2549,2576,2590,2602,2606,2610,2618,2647,2666,2670,2681,2686,2691,2696,2701,2706,2710,2714,2718,2746],{"__ignoreMap":63},[67,2470,2471,2474,2477,2480,2483],{"class":69,"line":70},[67,2472,2473],{"class":163},"import",[67,2475,2476],{"class":104}," axios ",[67,2478,2479],{"class":163},"from",[67,2481,2482],{"class":77}," \"axios\"",[67,2484,2485],{"class":104},";\n",[67,2487,2488],{"class":69,"line":251},[67,2489,989],{"emptyLinePlaceholder":988},[67,2491,2492,2495,2498],{"class":69,"line":264},[67,2493,2494],{"class":163},"class",[67,2496,2497],{"class":73}," User",[67,2499,2500],{"class":104}," {\n",[67,2502,2503,2506,2510,2513,2516],{"class":69,"line":280},[67,2504,2505],{"class":163},"  private",[67,2507,2509],{"class":2508},"s4XuR"," emailServiceUrl",[67,2511,2512],{"class":163}," =",[67,2514,2515],{"class":77}," `emailService.example\u002Fapi`",[67,2517,2485],{"class":104},[67,2519,2520,2522,2525,2527,2530],{"class":69,"line":288},[67,2521,2505],{"class":163},[67,2523,2524],{"class":2508}," email",[67,2526,1390],{"class":163},[67,2528,2529],{"class":113}," string",[67,2531,2485],{"class":104},[67,2533,2534,2536,2539,2541,2543],{"class":69,"line":781},[67,2535,2505],{"class":163},[67,2537,2538],{"class":2508}," name",[67,2540,1390],{"class":163},[67,2542,2529],{"class":113},[67,2544,2485],{"class":104},[67,2546,2547],{"class":69,"line":792},[67,2548,989],{"emptyLinePlaceholder":988},[67,2550,2551,2554,2557,2560,2562,2564,2566,2569,2571,2573],{"class":69,"line":803},[67,2552,2553],{"class":163},"  constructor",[67,2555,2556],{"class":104},"(",[67,2558,2559],{"class":2508},"name",[67,2561,1390],{"class":163},[67,2563,2529],{"class":113},[67,2565,944],{"class":104},[67,2567,2568],{"class":2508},"email",[67,2570,1390],{"class":163},[67,2572,2529],{"class":113},[67,2574,2575],{"class":104},") {\n",[67,2577,2578,2581,2584,2587],{"class":69,"line":814},[67,2579,2580],{"class":113},"    this",[67,2582,2583],{"class":104},".name ",[67,2585,2586],{"class":163},"=",[67,2588,2589],{"class":104}," name;\n",[67,2591,2592,2594,2597,2599],{"class":69,"line":825},[67,2593,2580],{"class":113},[67,2595,2596],{"class":104},".email ",[67,2598,2586],{"class":163},[67,2600,2601],{"class":104}," email;\n",[67,2603,2604],{"class":69,"line":836},[67,2605,1596],{"class":104},[67,2607,2608],{"class":69,"line":847},[67,2609,989],{"emptyLinePlaceholder":988},[67,2611,2612,2615],{"class":69,"line":858},[67,2613,2614],{"class":73},"  sendGreetingEmail",[67,2616,2617],{"class":104},"() {\n",[67,2619,2620,2623,2626,2628,2630,2632,2634,2637,2640,2642,2645],{"class":69,"line":869},[67,2621,2622],{"class":163},"    const",[67,2624,2625],{"class":104}," { ",[67,2627,2559],{"class":113},[67,2629,944],{"class":104},[67,2631,2568],{"class":113},[67,2633,944],{"class":104},[67,2635,2636],{"class":113},"emailServiceUrl",[67,2638,2639],{"class":104}," } ",[67,2641,2586],{"class":163},[67,2643,2644],{"class":113}," this",[67,2646,2485],{"class":104},[67,2648,2649,2651,2654,2656,2659,2661,2664],{"class":69,"line":877},[67,2650,2622],{"class":163},[67,2652,2653],{"class":113}," body",[67,2655,2512],{"class":163},[67,2657,2658],{"class":77}," `Hey ${",[67,2660,2559],{"class":104},[67,2662,2663],{"class":77},"}. Welcome to our app!`",[67,2665,2485],{"class":104},[67,2667,2668],{"class":69,"line":886},[67,2669,989],{"emptyLinePlaceholder":988},[67,2671,2672,2675,2678],{"class":69,"line":894},[67,2673,2674],{"class":163},"    return",[67,2676,2677],{"class":73}," axios",[67,2679,2680],{"class":104},"(emailServiceUrl, {\n",[67,2682,2683],{"class":69,"line":905},[67,2684,2685],{"class":104},"      data: {\n",[67,2687,2688],{"class":69,"line":916},[67,2689,2690],{"class":104},"        email,\n",[67,2692,2693],{"class":69,"line":927},[67,2694,2695],{"class":104},"        body,\n",[67,2697,2698],{"class":69,"line":935},[67,2699,2700],{"class":104},"      },\n",[67,2702,2703],{"class":69,"line":958},[67,2704,2705],{"class":104},"    });\n",[67,2707,2708],{"class":69,"line":971},[67,2709,1596],{"class":104},[67,2711,2712],{"class":69,"line":985},[67,2713,1601],{"class":104},[67,2715,2716],{"class":69,"line":992},[67,2717,989],{"emptyLinePlaceholder":988},[67,2719,2720,2723,2726,2728,2731,2733,2735,2738,2740,2743],{"class":69,"line":1000},[67,2721,2722],{"class":163},"const",[67,2724,2725],{"class":113}," user",[67,2727,2512],{"class":163},[67,2729,2730],{"class":163}," new",[67,2732,2497],{"class":73},[67,2734,2556],{"class":104},[67,2736,2737],{"class":77},"\"John\"",[67,2739,944],{"class":104},[67,2741,2742],{"class":77},"\"email@example.com\"",[67,2744,2745],{"class":104},");\n",[67,2747,2748,2751,2754],{"class":69,"line":1009},[67,2749,2750],{"class":104},"user.",[67,2752,2753],{"class":73},"sendGreetingEmail",[67,2755,2756],{"class":104},"();\n",[11,2758,2759,2760,2763],{},"As you can see in the example above, this ",[53,2761,2762],{},"User"," class is responsible for many things. It contains user details, sending a welcome email, contains the email service url, constructs and sends the email message. Think of the code smells here. Why would the user need to know about which email service to use? Why would the user need to handle sending a welcome email?",[11,2765,2766],{},"Let's refactor the above example to conform to SRP, so each class has only one reason to change.",[58,2768,2770],{"className":2464,"code":2769,"language":2466,"meta":63,"style":63},"import axios from \"axios\";\n\nclass EmailClient {\n  private endpoint = `emailService.example\u002Fapi`;\n\n  public send(to: string, body: any) {\n    const { endpoint } = this;\n\n    return axios(endpoint, {\n      data: {\n        email: to,\n        body,\n      },\n    });\n  }\n}\n\nclass User {\n  private email: string;\n  private name: string;\n\n  constructor(name: string, email: string) {\n    this.name = name;\n    this.email = email;\n  }\n\n  getName(): string {\n    return this.name;\n  }\n\n  getEmail(): string {\n    return this.email;\n  }\n}\n\nclass WelcomeUserService {\n  private emailService: EmailClient;\n\n  constructor() {\n    this.emailService = new EmailClient();\n  }\n\n  public handle(user: User) {\n    const name = user.getName();\n    const email = user.getEmail();\n    const body = `Hey ${name}. Welcome to our app!`;\n\n    return this.emailService.send(email, body);\n  }\n}\n\nconst welcomeUserService = new WelcomeUserService();\nconst user = new User(\"John\", \"email@example.com\");\n\nwelcomeUserService.handle(user);\n",[53,2771,2772,2784,2788,2797,2810,2814,2843,2860,2864,2873,2877,2882,2886,2890,2894,2898,2902,2906,2914,2926,2938,2942,2964,2974,2984,2988,2992,3006,3015,3019,3023,3036,3045,3049,3053,3057,3066,3079,3083,3090,3106,3111,3116,3135,3152,3168,3185,3190,3206,3211,3216,3221,3237,3260,3265],{"__ignoreMap":63},[67,2773,2774,2776,2778,2780,2782],{"class":69,"line":70},[67,2775,2473],{"class":163},[67,2777,2476],{"class":104},[67,2779,2479],{"class":163},[67,2781,2482],{"class":77},[67,2783,2485],{"class":104},[67,2785,2786],{"class":69,"line":251},[67,2787,989],{"emptyLinePlaceholder":988},[67,2789,2790,2792,2795],{"class":69,"line":264},[67,2791,2494],{"class":163},[67,2793,2794],{"class":73}," EmailClient",[67,2796,2500],{"class":104},[67,2798,2799,2801,2804,2806,2808],{"class":69,"line":280},[67,2800,2505],{"class":163},[67,2802,2803],{"class":2508}," endpoint",[67,2805,2512],{"class":163},[67,2807,2515],{"class":77},[67,2809,2485],{"class":104},[67,2811,2812],{"class":69,"line":288},[67,2813,989],{"emptyLinePlaceholder":988},[67,2815,2816,2819,2822,2824,2827,2829,2831,2833,2836,2838,2841],{"class":69,"line":781},[67,2817,2818],{"class":163},"  public",[67,2820,2821],{"class":73}," send",[67,2823,2556],{"class":104},[67,2825,2826],{"class":2508},"to",[67,2828,1390],{"class":163},[67,2830,2529],{"class":113},[67,2832,944],{"class":104},[67,2834,2835],{"class":2508},"body",[67,2837,1390],{"class":163},[67,2839,2840],{"class":113}," any",[67,2842,2575],{"class":104},[67,2844,2845,2847,2849,2852,2854,2856,2858],{"class":69,"line":792},[67,2846,2622],{"class":163},[67,2848,2625],{"class":104},[67,2850,2851],{"class":113},"endpoint",[67,2853,2639],{"class":104},[67,2855,2586],{"class":163},[67,2857,2644],{"class":113},[67,2859,2485],{"class":104},[67,2861,2862],{"class":69,"line":803},[67,2863,989],{"emptyLinePlaceholder":988},[67,2865,2866,2868,2870],{"class":69,"line":814},[67,2867,2674],{"class":163},[67,2869,2677],{"class":73},[67,2871,2872],{"class":104},"(endpoint, {\n",[67,2874,2875],{"class":69,"line":825},[67,2876,2685],{"class":104},[67,2878,2879],{"class":69,"line":836},[67,2880,2881],{"class":104},"        email: to,\n",[67,2883,2884],{"class":69,"line":847},[67,2885,2695],{"class":104},[67,2887,2888],{"class":69,"line":858},[67,2889,2700],{"class":104},[67,2891,2892],{"class":69,"line":869},[67,2893,2705],{"class":104},[67,2895,2896],{"class":69,"line":877},[67,2897,1596],{"class":104},[67,2899,2900],{"class":69,"line":886},[67,2901,1601],{"class":104},[67,2903,2904],{"class":69,"line":894},[67,2905,989],{"emptyLinePlaceholder":988},[67,2907,2908,2910,2912],{"class":69,"line":905},[67,2909,2494],{"class":163},[67,2911,2497],{"class":73},[67,2913,2500],{"class":104},[67,2915,2916,2918,2920,2922,2924],{"class":69,"line":916},[67,2917,2505],{"class":163},[67,2919,2524],{"class":2508},[67,2921,1390],{"class":163},[67,2923,2529],{"class":113},[67,2925,2485],{"class":104},[67,2927,2928,2930,2932,2934,2936],{"class":69,"line":927},[67,2929,2505],{"class":163},[67,2931,2538],{"class":2508},[67,2933,1390],{"class":163},[67,2935,2529],{"class":113},[67,2937,2485],{"class":104},[67,2939,2940],{"class":69,"line":935},[67,2941,989],{"emptyLinePlaceholder":988},[67,2943,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962],{"class":69,"line":958},[67,2945,2553],{"class":163},[67,2947,2556],{"class":104},[67,2949,2559],{"class":2508},[67,2951,1390],{"class":163},[67,2953,2529],{"class":113},[67,2955,944],{"class":104},[67,2957,2568],{"class":2508},[67,2959,1390],{"class":163},[67,2961,2529],{"class":113},[67,2963,2575],{"class":104},[67,2965,2966,2968,2970,2972],{"class":69,"line":971},[67,2967,2580],{"class":113},[67,2969,2583],{"class":104},[67,2971,2586],{"class":163},[67,2973,2589],{"class":104},[67,2975,2976,2978,2980,2982],{"class":69,"line":985},[67,2977,2580],{"class":113},[67,2979,2596],{"class":104},[67,2981,2586],{"class":163},[67,2983,2601],{"class":104},[67,2985,2986],{"class":69,"line":992},[67,2987,1596],{"class":104},[67,2989,2990],{"class":69,"line":1000},[67,2991,989],{"emptyLinePlaceholder":988},[67,2993,2994,2997,3000,3002,3004],{"class":69,"line":1009},[67,2995,2996],{"class":73},"  getName",[67,2998,2999],{"class":104},"()",[67,3001,1390],{"class":163},[67,3003,2529],{"class":113},[67,3005,2500],{"class":104},[67,3007,3008,3010,3012],{"class":69,"line":1016},[67,3009,2674],{"class":163},[67,3011,2644],{"class":113},[67,3013,3014],{"class":104},".name;\n",[67,3016,3017],{"class":69,"line":1025},[67,3018,1596],{"class":104},[67,3020,3021],{"class":69,"line":1034},[67,3022,989],{"emptyLinePlaceholder":988},[67,3024,3025,3028,3030,3032,3034],{"class":69,"line":1043},[67,3026,3027],{"class":73},"  getEmail",[67,3029,2999],{"class":104},[67,3031,1390],{"class":163},[67,3033,2529],{"class":113},[67,3035,2500],{"class":104},[67,3037,3038,3040,3042],{"class":69,"line":1054},[67,3039,2674],{"class":163},[67,3041,2644],{"class":113},[67,3043,3044],{"class":104},".email;\n",[67,3046,3047],{"class":69,"line":1061},[67,3048,1596],{"class":104},[67,3050,3051],{"class":69,"line":1068},[67,3052,1601],{"class":104},[67,3054,3055],{"class":69,"line":1075},[67,3056,989],{"emptyLinePlaceholder":988},[67,3058,3059,3061,3064],{"class":69,"line":1085},[67,3060,2494],{"class":163},[67,3062,3063],{"class":73}," WelcomeUserService",[67,3065,2500],{"class":104},[67,3067,3068,3070,3073,3075,3077],{"class":69,"line":1095},[67,3069,2505],{"class":163},[67,3071,3072],{"class":2508}," emailService",[67,3074,1390],{"class":163},[67,3076,2794],{"class":73},[67,3078,2485],{"class":104},[67,3080,3081],{"class":69,"line":1104},[67,3082,989],{"emptyLinePlaceholder":988},[67,3084,3086,3088],{"class":69,"line":3085},39,[67,3087,2553],{"class":163},[67,3089,2617],{"class":104},[67,3091,3093,3095,3098,3100,3102,3104],{"class":69,"line":3092},40,[67,3094,2580],{"class":113},[67,3096,3097],{"class":104},".emailService ",[67,3099,2586],{"class":163},[67,3101,2730],{"class":163},[67,3103,2794],{"class":73},[67,3105,2756],{"class":104},[67,3107,3109],{"class":69,"line":3108},41,[67,3110,1596],{"class":104},[67,3112,3114],{"class":69,"line":3113},42,[67,3115,989],{"emptyLinePlaceholder":988},[67,3117,3119,3121,3124,3126,3129,3131,3133],{"class":69,"line":3118},43,[67,3120,2818],{"class":163},[67,3122,3123],{"class":73}," handle",[67,3125,2556],{"class":104},[67,3127,3128],{"class":2508},"user",[67,3130,1390],{"class":163},[67,3132,2497],{"class":73},[67,3134,2575],{"class":104},[67,3136,3138,3140,3142,3144,3147,3150],{"class":69,"line":3137},44,[67,3139,2622],{"class":163},[67,3141,2538],{"class":113},[67,3143,2512],{"class":163},[67,3145,3146],{"class":104}," user.",[67,3148,3149],{"class":73},"getName",[67,3151,2756],{"class":104},[67,3153,3155,3157,3159,3161,3163,3166],{"class":69,"line":3154},45,[67,3156,2622],{"class":163},[67,3158,2524],{"class":113},[67,3160,2512],{"class":163},[67,3162,3146],{"class":104},[67,3164,3165],{"class":73},"getEmail",[67,3167,2756],{"class":104},[67,3169,3171,3173,3175,3177,3179,3181,3183],{"class":69,"line":3170},46,[67,3172,2622],{"class":163},[67,3174,2653],{"class":113},[67,3176,2512],{"class":163},[67,3178,2658],{"class":77},[67,3180,2559],{"class":104},[67,3182,2663],{"class":77},[67,3184,2485],{"class":104},[67,3186,3188],{"class":69,"line":3187},47,[67,3189,989],{"emptyLinePlaceholder":988},[67,3191,3193,3195,3197,3200,3203],{"class":69,"line":3192},48,[67,3194,2674],{"class":163},[67,3196,2644],{"class":113},[67,3198,3199],{"class":104},".emailService.",[67,3201,3202],{"class":73},"send",[67,3204,3205],{"class":104},"(email, body);\n",[67,3207,3209],{"class":69,"line":3208},49,[67,3210,1596],{"class":104},[67,3212,3214],{"class":69,"line":3213},50,[67,3215,1601],{"class":104},[67,3217,3219],{"class":69,"line":3218},51,[67,3220,989],{"emptyLinePlaceholder":988},[67,3222,3224,3226,3229,3231,3233,3235],{"class":69,"line":3223},52,[67,3225,2722],{"class":163},[67,3227,3228],{"class":113}," welcomeUserService",[67,3230,2512],{"class":163},[67,3232,2730],{"class":163},[67,3234,3063],{"class":73},[67,3236,2756],{"class":104},[67,3238,3240,3242,3244,3246,3248,3250,3252,3254,3256,3258],{"class":69,"line":3239},53,[67,3241,2722],{"class":163},[67,3243,2725],{"class":113},[67,3245,2512],{"class":163},[67,3247,2730],{"class":163},[67,3249,2497],{"class":73},[67,3251,2556],{"class":104},[67,3253,2737],{"class":77},[67,3255,944],{"class":104},[67,3257,2742],{"class":77},[67,3259,2745],{"class":104},[67,3261,3263],{"class":69,"line":3262},54,[67,3264,989],{"emptyLinePlaceholder":988},[67,3266,3268,3271,3274],{"class":69,"line":3267},55,[67,3269,3270],{"class":104},"welcomeUserService.",[67,3272,3273],{"class":73},"handle",[67,3275,3276],{"class":104},"(user);\n",[11,3278,3279,3280],{},"When you compare the two code snippets, the refactored one is longer. ",[21,3281,3282],{},"So let's address this before deep-diving into the refactor. Yes, the refactored code is a bit longer but that doesn't mean that you will lose more time writing this code. In fact, you will save time in the long run when you go back reading and trying to understand your code. So it's a tradeoff, and a tradeoff that you should be more than happy to make.",[11,3284,3285,3286,3288,3289,3291,3292,3295,3296,3299,3300,3303],{},"After we refactored our ",[53,3287,2762],{}," class we ended up having three classes. Now, the ",[53,3290,2762],{}," class is only responsible for keeping the user details - SRP achieved. But what happened with the other functionality? We extracted that functionality to separate classes. We have an ",[53,3293,3294],{},"EmailClient"," class, that is responsible for sending out emails (",[1738,3297,3298],{},"any kind of emails, not just welcome emails","). Now we have a class that we can reuse now only for our users but throughout our application. The ",[53,3301,3302],{},"WelcomeUserService"," class handles sending out an email to the user when he joins our app. It is responsible for generating the body for the email and sending out the email.",[11,3305,3306],{},"There we have it. We refactored one class that doesn't follow SRP to three classes that do. Extending any of these classes will be easier now because they are not tightly coupled.",[11,3308,3309],{},[21,3310,3311],{},"How to notice code that needs to be refactored using SRP",[26,3313,3314,3317,3326],{},[29,3315,3316],{},"Prefixed functions that should be extracted to their own class,",[29,3318,3319,3320,2004,3322,3325],{},"Mixing separate entities into one class (ex. encapsulating",[53,3321,2762],{},[53,3323,3324],{},"UserRole"," into one class)",[29,3327,3328,3329,2004,3331,3325],{},"Mixing separate concerns into one class (ex. encapsulating ",[53,3330,2762],{},[53,3332,3333],{},"AuthService",[1758,3335,3337],{"id":3336},"openclosed-principle-ocp","Open\u002FClosed Principle (OCP)",[11,3339,3340],{},"People usually stop reading and trying to understand the rest of the SOLID principles after going through SRP because they feel intimidated by the definitions and names of the remaining principles, but don't give up just yet!",[11,3342,3343,3346],{},[21,3344,3345],{},"The OCP requires that a class (other entities as well - functions, modules) should be open for extension but closed for modification."," This principle can be confusing at the beginning because its name is counterintuitive, but once you understand it, it's the principle that will save you the most time in terms of development in the future. Its goal is to get your application to a stable state, a state where the application's core can never be broken.",[11,3348,3349,3350,3353,3354,3357,3358,3361,3362,3365],{},"Imagine we have an ",[1738,3351,3352],{},"e-commerce"," application that needs to handle different payment methods. In the beginning, lets say we have a ",[53,3355,3356],{},"Checkout"," class that has a ",[53,3359,3360],{},"process"," method that processes a ",[53,3363,3364],{},"Cart"," using the payment method the user selected.",[58,3367,3369],{"className":2464,"code":3368,"language":2466,"meta":63,"style":63},"class Payment {\n  creditCard(cart: Cart) {\n    \u002F\u002F handle payment using credit card\n  }\n}\n\nclass Checkout {\n  private cart: Cart;\n\n  process() {\n    new Payment().creditCard(this.cart);\n  }\n}\n",[53,3370,3371,3380,3397,3403,3407,3411,3415,3424,3437,3441,3448,3469,3473],{"__ignoreMap":63},[67,3372,3373,3375,3378],{"class":69,"line":70},[67,3374,2494],{"class":163},[67,3376,3377],{"class":73}," Payment",[67,3379,2500],{"class":104},[67,3381,3382,3385,3387,3390,3392,3395],{"class":69,"line":251},[67,3383,3384],{"class":73},"  creditCard",[67,3386,2556],{"class":104},[67,3388,3389],{"class":2508},"cart",[67,3391,1390],{"class":163},[67,3393,3394],{"class":73}," Cart",[67,3396,2575],{"class":104},[67,3398,3399],{"class":69,"line":264},[67,3400,3402],{"class":3401},"sJ8bj","    \u002F\u002F handle payment using credit card\n",[67,3404,3405],{"class":69,"line":280},[67,3406,1596],{"class":104},[67,3408,3409],{"class":69,"line":288},[67,3410,1601],{"class":104},[67,3412,3413],{"class":69,"line":781},[67,3414,989],{"emptyLinePlaceholder":988},[67,3416,3417,3419,3422],{"class":69,"line":792},[67,3418,2494],{"class":163},[67,3420,3421],{"class":73}," Checkout",[67,3423,2500],{"class":104},[67,3425,3426,3428,3431,3433,3435],{"class":69,"line":803},[67,3427,2505],{"class":163},[67,3429,3430],{"class":2508}," cart",[67,3432,1390],{"class":163},[67,3434,3394],{"class":73},[67,3436,2485],{"class":104},[67,3438,3439],{"class":69,"line":814},[67,3440,989],{"emptyLinePlaceholder":988},[67,3442,3443,3446],{"class":69,"line":825},[67,3444,3445],{"class":73},"  process",[67,3447,2617],{"class":104},[67,3449,3450,3453,3455,3458,3461,3463,3466],{"class":69,"line":836},[67,3451,3452],{"class":163},"    new",[67,3454,3377],{"class":73},[67,3456,3457],{"class":104},"().",[67,3459,3460],{"class":73},"creditCard",[67,3462,2556],{"class":104},[67,3464,3465],{"class":113},"this",[67,3467,3468],{"class":104},".cart);\n",[67,3470,3471],{"class":69,"line":847},[67,3472,1596],{"class":104},[67,3474,3475],{"class":69,"line":858},[67,3476,1601],{"class":104},[11,3478,3479,3480,3483,3484,3487,3488,3490,3491,3493,3494,3497,3498,3501],{},"But, what if we want to add more payment methods - PayPal, Coupons, Bank Transfer, etc. They all have different underlying implementations, so we have to implement separate methods within our ",[53,3481,3482],{},"Payment"," class for each of these payment methods. Now, we have to add ",[53,3485,3486],{},"if"," statements within our ",[53,3489,3360],{}," method within the ",[53,3492,3356],{}," class to handle different cases. So, if we decide to add another payment method, we have to ",[21,3495,3496],{},"modify"," our ****existing implementation to add another ",[53,3499,3500],{},"else if"," statement to handle this case. Considering this principle it means that our code design is bad and we should consider another approach.",[58,3503,3505],{"className":2464,"code":3504,"language":2466,"meta":63,"style":63},"class Payment {\n  private cart: Cart;\n\n  constructor(cart: Cart) {\n    this.cart = cart;\n  }\n\n  creditCard() {\n    \u002F\u002F handle payment using credit card\n  }\n\n  payPal() {\n    \u002F\u002F handle payment using paypal\n  }\n\n  bankTransfer() {\n    \u002F\u002F handle payment using bank transfer\n  }\n\n  coupon() {\n    \u002F\u002F handle payment using bank transfer\n  }\n}\n\nclass Checkout {\n  private cart: Cart;\n\n  constructor(cart: Cart) {\n    this.cart = cart;\n  }\n\n  process() {\n    const paymentMethod = request().get(\"payment_method\"); \u002F\u002F getting user's selection\n    const payment = new Payment(this.card);\n\n    if (paymentMethod === \"credit-card\") {\n      payment.creditCard();\n    } else if (paymentMethod === \"paypal\") {\n      payment.paypal();\n    } else if (paymentMethod === \"bank-transfer\") {\n      payment.bankTransfer();\n    } else {\n      payment.coupon();\n    }\n  }\n}\n",[53,3506,3507,3515,3527,3531,3545,3557,3561,3565,3571,3575,3579,3583,3590,3595,3599,3603,3610,3615,3619,3623,3630,3634,3638,3642,3646,3654,3666,3670,3684,3694,3698,3702,3708,3736,3756,3760,3776,3785,3805,3814,3831,3840,3848,3857,3861,3865],{"__ignoreMap":63},[67,3508,3509,3511,3513],{"class":69,"line":70},[67,3510,2494],{"class":163},[67,3512,3377],{"class":73},[67,3514,2500],{"class":104},[67,3516,3517,3519,3521,3523,3525],{"class":69,"line":251},[67,3518,2505],{"class":163},[67,3520,3430],{"class":2508},[67,3522,1390],{"class":163},[67,3524,3394],{"class":73},[67,3526,2485],{"class":104},[67,3528,3529],{"class":69,"line":264},[67,3530,989],{"emptyLinePlaceholder":988},[67,3532,3533,3535,3537,3539,3541,3543],{"class":69,"line":280},[67,3534,2553],{"class":163},[67,3536,2556],{"class":104},[67,3538,3389],{"class":2508},[67,3540,1390],{"class":163},[67,3542,3394],{"class":73},[67,3544,2575],{"class":104},[67,3546,3547,3549,3552,3554],{"class":69,"line":288},[67,3548,2580],{"class":113},[67,3550,3551],{"class":104},".cart ",[67,3553,2586],{"class":163},[67,3555,3556],{"class":104}," cart;\n",[67,3558,3559],{"class":69,"line":781},[67,3560,1596],{"class":104},[67,3562,3563],{"class":69,"line":792},[67,3564,989],{"emptyLinePlaceholder":988},[67,3566,3567,3569],{"class":69,"line":803},[67,3568,3384],{"class":73},[67,3570,2617],{"class":104},[67,3572,3573],{"class":69,"line":814},[67,3574,3402],{"class":3401},[67,3576,3577],{"class":69,"line":825},[67,3578,1596],{"class":104},[67,3580,3581],{"class":69,"line":836},[67,3582,989],{"emptyLinePlaceholder":988},[67,3584,3585,3588],{"class":69,"line":847},[67,3586,3587],{"class":73},"  payPal",[67,3589,2617],{"class":104},[67,3591,3592],{"class":69,"line":858},[67,3593,3594],{"class":3401},"    \u002F\u002F handle payment using paypal\n",[67,3596,3597],{"class":69,"line":869},[67,3598,1596],{"class":104},[67,3600,3601],{"class":69,"line":877},[67,3602,989],{"emptyLinePlaceholder":988},[67,3604,3605,3608],{"class":69,"line":886},[67,3606,3607],{"class":73},"  bankTransfer",[67,3609,2617],{"class":104},[67,3611,3612],{"class":69,"line":894},[67,3613,3614],{"class":3401},"    \u002F\u002F handle payment using bank transfer\n",[67,3616,3617],{"class":69,"line":905},[67,3618,1596],{"class":104},[67,3620,3621],{"class":69,"line":916},[67,3622,989],{"emptyLinePlaceholder":988},[67,3624,3625,3628],{"class":69,"line":927},[67,3626,3627],{"class":73},"  coupon",[67,3629,2617],{"class":104},[67,3631,3632],{"class":69,"line":935},[67,3633,3614],{"class":3401},[67,3635,3636],{"class":69,"line":958},[67,3637,1596],{"class":104},[67,3639,3640],{"class":69,"line":971},[67,3641,1601],{"class":104},[67,3643,3644],{"class":69,"line":985},[67,3645,989],{"emptyLinePlaceholder":988},[67,3647,3648,3650,3652],{"class":69,"line":992},[67,3649,2494],{"class":163},[67,3651,3421],{"class":73},[67,3653,2500],{"class":104},[67,3655,3656,3658,3660,3662,3664],{"class":69,"line":1000},[67,3657,2505],{"class":163},[67,3659,3430],{"class":2508},[67,3661,1390],{"class":163},[67,3663,3394],{"class":73},[67,3665,2485],{"class":104},[67,3667,3668],{"class":69,"line":1009},[67,3669,989],{"emptyLinePlaceholder":988},[67,3671,3672,3674,3676,3678,3680,3682],{"class":69,"line":1016},[67,3673,2553],{"class":163},[67,3675,2556],{"class":104},[67,3677,3389],{"class":2508},[67,3679,1390],{"class":163},[67,3681,3394],{"class":73},[67,3683,2575],{"class":104},[67,3685,3686,3688,3690,3692],{"class":69,"line":1025},[67,3687,2580],{"class":113},[67,3689,3551],{"class":104},[67,3691,2586],{"class":163},[67,3693,3556],{"class":104},[67,3695,3696],{"class":69,"line":1034},[67,3697,1596],{"class":104},[67,3699,3700],{"class":69,"line":1043},[67,3701,989],{"emptyLinePlaceholder":988},[67,3703,3704,3706],{"class":69,"line":1054},[67,3705,3445],{"class":73},[67,3707,2617],{"class":104},[67,3709,3710,3712,3715,3717,3720,3722,3725,3727,3730,3733],{"class":69,"line":1061},[67,3711,2622],{"class":163},[67,3713,3714],{"class":113}," paymentMethod",[67,3716,2512],{"class":163},[67,3718,3719],{"class":73}," request",[67,3721,3457],{"class":104},[67,3723,3724],{"class":73},"get",[67,3726,2556],{"class":104},[67,3728,3729],{"class":77},"\"payment_method\"",[67,3731,3732],{"class":104},"); ",[67,3734,3735],{"class":3401},"\u002F\u002F getting user's selection\n",[67,3737,3738,3740,3743,3745,3747,3749,3751,3753],{"class":69,"line":1068},[67,3739,2622],{"class":163},[67,3741,3742],{"class":113}," payment",[67,3744,2512],{"class":163},[67,3746,2730],{"class":163},[67,3748,3377],{"class":73},[67,3750,2556],{"class":104},[67,3752,3465],{"class":113},[67,3754,3755],{"class":104},".card);\n",[67,3757,3758],{"class":69,"line":1075},[67,3759,989],{"emptyLinePlaceholder":988},[67,3761,3762,3765,3768,3771,3774],{"class":69,"line":1085},[67,3763,3764],{"class":163},"    if",[67,3766,3767],{"class":104}," (paymentMethod ",[67,3769,3770],{"class":163},"===",[67,3772,3773],{"class":77}," \"credit-card\"",[67,3775,2575],{"class":104},[67,3777,3778,3781,3783],{"class":69,"line":1095},[67,3779,3780],{"class":104},"      payment.",[67,3782,3460],{"class":73},[67,3784,2756],{"class":104},[67,3786,3787,3790,3793,3796,3798,3800,3803],{"class":69,"line":1104},[67,3788,3789],{"class":104},"    } ",[67,3791,3792],{"class":163},"else",[67,3794,3795],{"class":163}," if",[67,3797,3767],{"class":104},[67,3799,3770],{"class":163},[67,3801,3802],{"class":77}," \"paypal\"",[67,3804,2575],{"class":104},[67,3806,3807,3809,3812],{"class":69,"line":3085},[67,3808,3780],{"class":104},[67,3810,3811],{"class":73},"paypal",[67,3813,2756],{"class":104},[67,3815,3816,3818,3820,3822,3824,3826,3829],{"class":69,"line":3092},[67,3817,3789],{"class":104},[67,3819,3792],{"class":163},[67,3821,3795],{"class":163},[67,3823,3767],{"class":104},[67,3825,3770],{"class":163},[67,3827,3828],{"class":77}," \"bank-transfer\"",[67,3830,2575],{"class":104},[67,3832,3833,3835,3838],{"class":69,"line":3108},[67,3834,3780],{"class":104},[67,3836,3837],{"class":73},"bankTransfer",[67,3839,2756],{"class":104},[67,3841,3842,3844,3846],{"class":69,"line":3113},[67,3843,3789],{"class":104},[67,3845,3792],{"class":163},[67,3847,2500],{"class":104},[67,3849,3850,3852,3855],{"class":69,"line":3118},[67,3851,3780],{"class":104},[67,3853,3854],{"class":73},"coupon",[67,3856,2756],{"class":104},[67,3858,3859],{"class":69,"line":3137},[67,3860,1557],{"class":104},[67,3862,3863],{"class":69,"line":3154},[67,3864,1596],{"class":104},[67,3866,3867],{"class":69,"line":3170},[67,3868,1601],{"class":104},[11,3870,3871,3872,3874,3875,3877,3878,3880,3881,3883,3884,3887],{},"What would happen if we need to add another payment method? We would have to change the ",[53,3873,3360],{}," method in the ",[53,3876,3356],{}," class and include another ",[53,3879,3500],{}," statement in the ",[53,3882,3360],{}," method which violates the ",[1738,3885,3886],{},"closed for modification"," part of this principle. This calls for a code design change.",[11,3889,3890,3891,3894],{},"To refactor this code and satisfy the requirements of the principle, we can use the ",[21,3892,3893],{},"Factory Pattern",". The Factory pattern is a creational pattern that allows us to create different instances of a class on runtime (more about the Factory Pattern can be found online). This means that we don't have to care about which method is going to be called because a proper class instance will be instantiated on runtime based on the client's selection.",[11,3896,3897,3898,3901,3902,3905,3906,3908],{},"Knowing this, let's refactor the example above to follow the requirements of OCP. We can define an interface and define classes for each of the payments that extend that interface and implement its methods. We will also define a ",[53,3899,3900],{},"PayableFactory"," class that instantiates our desired ",[53,3903,3904],{},"Payable"," instance and we use that in the checkout. That way, the ",[53,3907,3356],{}," class is not going to be modified in case we need to add another payment method and is compliant with the requirements of OCP.",[58,3910,3912],{"className":2464,"code":3911,"language":2466,"meta":63,"style":63},"interface Payable {\n  pay(cart: Cart);\n}\n\nclass BankTransfer implements Payable {\n  pay(cart: Cart) {\n    \u002F\u002F Handle bank transfer payment\n  }\n}\n\nclass CreditCard implements Payable {\n  pay(cart: Cart) {\n    \u002F\u002F Handle credit card payment\n  }\n}\n\nclass PayPal implements Payable {\n  pay(cart: Cart) {\n    \u002F\u002F Handle PayPal payment\n  }\n}\n\nclass PayableFactory {\n  static make(type: string) {\n    if(paymentMethod === 'credit-card') {\n      return new CreditCard();\n    } else if (paymentMethod === 'paypal') {\n      return new PayPal();\n    } else (paymentMethod === 'bank-transfer') {\n      return new BankTransfer();\n  }\n}\n\nclass Checkout {\n  private cart: Cart;\n\n  constructor(cart: Cart) {\n    this.cart = cart;\n  }\n\n  process() {\n    const paymentMethod = request().get('payment_method') \u002F\u002F getting user's selection\n    const paymentFactory = new PaymentFactory(paymentMethod);\n\n    paymentFactory.pay(cart);\n  }\n}\n",[53,3913,3914,3924,3939,3943,3947,3961,3975,3980,3984,3988,3992,4005,4019,4024,4028,4032,4036,4049,4063,4068,4072,4076,4080,4089,4108,4122,4133,4150,4160,4175,4185,4189,4193,4197,4205,4217,4221,4235,4245,4249,4253,4259,4283,4300,4304,4315,4319],{"__ignoreMap":63},[67,3915,3916,3919,3922],{"class":69,"line":70},[67,3917,3918],{"class":163},"interface",[67,3920,3921],{"class":73}," Payable",[67,3923,2500],{"class":104},[67,3925,3926,3929,3931,3933,3935,3937],{"class":69,"line":251},[67,3927,3928],{"class":73},"  pay",[67,3930,2556],{"class":104},[67,3932,3389],{"class":2508},[67,3934,1390],{"class":163},[67,3936,3394],{"class":73},[67,3938,2745],{"class":104},[67,3940,3941],{"class":69,"line":264},[67,3942,1601],{"class":104},[67,3944,3945],{"class":69,"line":280},[67,3946,989],{"emptyLinePlaceholder":988},[67,3948,3949,3951,3954,3957,3959],{"class":69,"line":288},[67,3950,2494],{"class":163},[67,3952,3953],{"class":73}," BankTransfer",[67,3955,3956],{"class":163}," implements",[67,3958,3921],{"class":73},[67,3960,2500],{"class":104},[67,3962,3963,3965,3967,3969,3971,3973],{"class":69,"line":781},[67,3964,3928],{"class":73},[67,3966,2556],{"class":104},[67,3968,3389],{"class":2508},[67,3970,1390],{"class":163},[67,3972,3394],{"class":73},[67,3974,2575],{"class":104},[67,3976,3977],{"class":69,"line":792},[67,3978,3979],{"class":3401},"    \u002F\u002F Handle bank transfer payment\n",[67,3981,3982],{"class":69,"line":803},[67,3983,1596],{"class":104},[67,3985,3986],{"class":69,"line":814},[67,3987,1601],{"class":104},[67,3989,3990],{"class":69,"line":825},[67,3991,989],{"emptyLinePlaceholder":988},[67,3993,3994,3996,3999,4001,4003],{"class":69,"line":836},[67,3995,2494],{"class":163},[67,3997,3998],{"class":73}," CreditCard",[67,4000,3956],{"class":163},[67,4002,3921],{"class":73},[67,4004,2500],{"class":104},[67,4006,4007,4009,4011,4013,4015,4017],{"class":69,"line":847},[67,4008,3928],{"class":73},[67,4010,2556],{"class":104},[67,4012,3389],{"class":2508},[67,4014,1390],{"class":163},[67,4016,3394],{"class":73},[67,4018,2575],{"class":104},[67,4020,4021],{"class":69,"line":858},[67,4022,4023],{"class":3401},"    \u002F\u002F Handle credit card payment\n",[67,4025,4026],{"class":69,"line":869},[67,4027,1596],{"class":104},[67,4029,4030],{"class":69,"line":877},[67,4031,1601],{"class":104},[67,4033,4034],{"class":69,"line":886},[67,4035,989],{"emptyLinePlaceholder":988},[67,4037,4038,4040,4043,4045,4047],{"class":69,"line":894},[67,4039,2494],{"class":163},[67,4041,4042],{"class":73}," PayPal",[67,4044,3956],{"class":163},[67,4046,3921],{"class":73},[67,4048,2500],{"class":104},[67,4050,4051,4053,4055,4057,4059,4061],{"class":69,"line":905},[67,4052,3928],{"class":73},[67,4054,2556],{"class":104},[67,4056,3389],{"class":2508},[67,4058,1390],{"class":163},[67,4060,3394],{"class":73},[67,4062,2575],{"class":104},[67,4064,4065],{"class":69,"line":916},[67,4066,4067],{"class":3401},"    \u002F\u002F Handle PayPal payment\n",[67,4069,4070],{"class":69,"line":927},[67,4071,1596],{"class":104},[67,4073,4074],{"class":69,"line":935},[67,4075,1601],{"class":104},[67,4077,4078],{"class":69,"line":958},[67,4079,989],{"emptyLinePlaceholder":988},[67,4081,4082,4084,4087],{"class":69,"line":971},[67,4083,2494],{"class":163},[67,4085,4086],{"class":73}," PayableFactory",[67,4088,2500],{"class":104},[67,4090,4091,4094,4097,4099,4102,4104,4106],{"class":69,"line":985},[67,4092,4093],{"class":163},"  static",[67,4095,4096],{"class":73}," make",[67,4098,2556],{"class":104},[67,4100,4101],{"class":2508},"type",[67,4103,1390],{"class":163},[67,4105,2529],{"class":113},[67,4107,2575],{"class":104},[67,4109,4110,4112,4115,4117,4120],{"class":69,"line":992},[67,4111,3764],{"class":163},[67,4113,4114],{"class":104},"(paymentMethod ",[67,4116,3770],{"class":163},[67,4118,4119],{"class":77}," 'credit-card'",[67,4121,2575],{"class":104},[67,4123,4124,4127,4129,4131],{"class":69,"line":1000},[67,4125,4126],{"class":163},"      return",[67,4128,2730],{"class":163},[67,4130,3998],{"class":73},[67,4132,2756],{"class":104},[67,4134,4135,4137,4139,4141,4143,4145,4148],{"class":69,"line":1009},[67,4136,3789],{"class":104},[67,4138,3792],{"class":163},[67,4140,3795],{"class":163},[67,4142,3767],{"class":104},[67,4144,3770],{"class":163},[67,4146,4147],{"class":77}," 'paypal'",[67,4149,2575],{"class":104},[67,4151,4152,4154,4156,4158],{"class":69,"line":1016},[67,4153,4126],{"class":163},[67,4155,2730],{"class":163},[67,4157,4042],{"class":73},[67,4159,2756],{"class":104},[67,4161,4162,4164,4166,4168,4170,4173],{"class":69,"line":1025},[67,4163,3789],{"class":104},[67,4165,3792],{"class":163},[67,4167,3767],{"class":104},[67,4169,3770],{"class":163},[67,4171,4172],{"class":77}," 'bank-transfer'",[67,4174,2575],{"class":104},[67,4176,4177,4179,4181,4183],{"class":69,"line":1034},[67,4178,4126],{"class":163},[67,4180,2730],{"class":163},[67,4182,3953],{"class":73},[67,4184,2756],{"class":104},[67,4186,4187],{"class":69,"line":1043},[67,4188,1596],{"class":104},[67,4190,4191],{"class":69,"line":1054},[67,4192,1601],{"class":104},[67,4194,4195],{"class":69,"line":1061},[67,4196,989],{"emptyLinePlaceholder":988},[67,4198,4199,4201,4203],{"class":69,"line":1068},[67,4200,2494],{"class":163},[67,4202,3421],{"class":73},[67,4204,2500],{"class":104},[67,4206,4207,4209,4211,4213,4215],{"class":69,"line":1075},[67,4208,2505],{"class":163},[67,4210,3430],{"class":2508},[67,4212,1390],{"class":163},[67,4214,3394],{"class":73},[67,4216,2485],{"class":104},[67,4218,4219],{"class":69,"line":1085},[67,4220,989],{"emptyLinePlaceholder":988},[67,4222,4223,4225,4227,4229,4231,4233],{"class":69,"line":1095},[67,4224,2553],{"class":163},[67,4226,2556],{"class":104},[67,4228,3389],{"class":2508},[67,4230,1390],{"class":163},[67,4232,3394],{"class":73},[67,4234,2575],{"class":104},[67,4236,4237,4239,4241,4243],{"class":69,"line":1104},[67,4238,2580],{"class":113},[67,4240,3551],{"class":104},[67,4242,2586],{"class":163},[67,4244,3556],{"class":104},[67,4246,4247],{"class":69,"line":3085},[67,4248,1596],{"class":104},[67,4250,4251],{"class":69,"line":3092},[67,4252,989],{"emptyLinePlaceholder":988},[67,4254,4255,4257],{"class":69,"line":3108},[67,4256,3445],{"class":73},[67,4258,2617],{"class":104},[67,4260,4261,4263,4265,4267,4269,4271,4273,4275,4278,4281],{"class":69,"line":3113},[67,4262,2622],{"class":163},[67,4264,3714],{"class":113},[67,4266,2512],{"class":163},[67,4268,3719],{"class":73},[67,4270,3457],{"class":104},[67,4272,3724],{"class":73},[67,4274,2556],{"class":104},[67,4276,4277],{"class":77},"'payment_method'",[67,4279,4280],{"class":104},") ",[67,4282,3735],{"class":3401},[67,4284,4285,4287,4290,4292,4294,4297],{"class":69,"line":3118},[67,4286,2622],{"class":163},[67,4288,4289],{"class":113}," paymentFactory",[67,4291,2512],{"class":163},[67,4293,2730],{"class":163},[67,4295,4296],{"class":73}," PaymentFactory",[67,4298,4299],{"class":104},"(paymentMethod);\n",[67,4301,4302],{"class":69,"line":3137},[67,4303,989],{"emptyLinePlaceholder":988},[67,4305,4306,4309,4312],{"class":69,"line":3154},[67,4307,4308],{"class":104},"    paymentFactory.",[67,4310,4311],{"class":73},"pay",[67,4313,4314],{"class":104},"(cart);\n",[67,4316,4317],{"class":69,"line":3170},[67,4318,1596],{"class":104},[67,4320,4321],{"class":69,"line":3187},[67,4322,1601],{"class":104},[11,4324,4325],{},[21,4326,4327],{},"How to notice code that needs to be refactored using OCP",[26,4329,4330,4341,4344],{},[29,4331,4332,4333,4335,4336,4335,4338,4340],{},"Conditional Code - a lot of ",[53,4334,3486],{}," ",[53,4337,3500],{},[53,4339,3792],{}," statements,",[29,4342,4343],{},"Multiple unit tests due to multiple execution paths,",[29,4345,4346],{},"Violation of the Single Responsibility Principle,",[1758,4348,4350],{"id":4349},"liskov-substitution-principle","Liskov Substitution Principle",[11,4352,4353,4356,4357,4360,4361,4366],{},[21,4354,4355],{},"LSP states that"," r",[21,4358,4359],{},"eplacing an instance of a class with its child class should not produce any negative side effects."," This rule, introduced by ",[1711,4362,4365],{"href":4363,"rel":4364},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBarbara_Liskov",[1715],"Barbara Liskov",", ensures us that changing one part of our system does not break other parts. To make this principle less confusing, we will break it down into multiple parts.",[11,4368,4369],{},"The first thing we notice for LSP is that its main focus is class inheritance. Let’s implement a straightforward and vivid example of how we can break the above principle (this time using a meme as a starting point).",[11,4371,4372],{},[1838,4373],{"alt":4350,"src":4374},"\u002Fimages\u002Fposts\u002Fduck-liskov.jpg",[11,4376,4377],{},"Let's translate the meme into valid OOP code.",[58,4379,4381],{"className":2464,"code":4380,"language":2466,"meta":63,"style":63},"abstract class Duck {\n  quack(): string {\n    return 'The duck is quacking';\n  },\n\n  fly(): string {\n    return 'The duck is flying';\n  },\n\n  swim(): string {\n    return 'The duck is swimming';\n  }\n}\n\nclass RubberDuck extends Duck {\n  quack(): string {\n    person = new Person();\n\n    if(!person.squeezesDuck(this)) {\n      throw new Error('The rubber duck cannot swim on its own');\n    }\n\n    return 'The duck is quacking';\n  }\n\n  fly() {\n    throw new Error('The rubber duck cannot fly');\n  }\n\n  swim(): string {\n    person = new Person();\n\n    if(!person.throwDuckInBath(this)) {\n      throw new Error('The rubber duck cannot swim on its own');\n    }\n\n    return 'The duck is swimming';\n  }\n}\n",[53,4382,4383,4396,4409,4418,4422,4426,4439,4448,4452,4456,4469,4478,4482,4486,4490,4504,4516,4530,4534,4556,4573,4577,4581,4589,4593,4597,4603,4619,4623,4627,4639,4651,4655,4674,4688,4692,4696,4704,4708],{"__ignoreMap":63},[67,4384,4385,4388,4391,4394],{"class":69,"line":70},[67,4386,4387],{"class":163},"abstract",[67,4389,4390],{"class":163}," class",[67,4392,4393],{"class":73}," Duck",[67,4395,2500],{"class":104},[67,4397,4398,4401,4403,4405,4407],{"class":69,"line":251},[67,4399,4400],{"class":73},"  quack",[67,4402,2999],{"class":104},[67,4404,1390],{"class":163},[67,4406,2529],{"class":113},[67,4408,2500],{"class":104},[67,4410,4411,4413,4416],{"class":69,"line":264},[67,4412,2674],{"class":163},[67,4414,4415],{"class":77}," 'The duck is quacking'",[67,4417,2485],{"class":104},[67,4419,4420],{"class":69,"line":280},[67,4421,1562],{"class":104},[67,4423,4424],{"class":69,"line":288},[67,4425,989],{"emptyLinePlaceholder":988},[67,4427,4428,4431,4433,4435,4437],{"class":69,"line":781},[67,4429,4430],{"class":73},"  fly",[67,4432,2999],{"class":104},[67,4434,1390],{"class":163},[67,4436,2529],{"class":113},[67,4438,2500],{"class":104},[67,4440,4441,4443,4446],{"class":69,"line":792},[67,4442,2674],{"class":163},[67,4444,4445],{"class":77}," 'The duck is flying'",[67,4447,2485],{"class":104},[67,4449,4450],{"class":69,"line":803},[67,4451,1562],{"class":104},[67,4453,4454],{"class":69,"line":814},[67,4455,989],{"emptyLinePlaceholder":988},[67,4457,4458,4461,4463,4465,4467],{"class":69,"line":825},[67,4459,4460],{"class":73},"  swim",[67,4462,2999],{"class":104},[67,4464,1390],{"class":163},[67,4466,2529],{"class":113},[67,4468,2500],{"class":104},[67,4470,4471,4473,4476],{"class":69,"line":836},[67,4472,2674],{"class":163},[67,4474,4475],{"class":77}," 'The duck is swimming'",[67,4477,2485],{"class":104},[67,4479,4480],{"class":69,"line":847},[67,4481,1596],{"class":104},[67,4483,4484],{"class":69,"line":858},[67,4485,1601],{"class":104},[67,4487,4488],{"class":69,"line":869},[67,4489,989],{"emptyLinePlaceholder":988},[67,4491,4492,4494,4497,4500,4502],{"class":69,"line":877},[67,4493,2494],{"class":163},[67,4495,4496],{"class":73}," RubberDuck",[67,4498,4499],{"class":163}," extends",[67,4501,4393],{"class":73},[67,4503,2500],{"class":104},[67,4505,4506,4508,4510,4512,4514],{"class":69,"line":886},[67,4507,4400],{"class":73},[67,4509,2999],{"class":104},[67,4511,1390],{"class":163},[67,4513,2529],{"class":113},[67,4515,2500],{"class":104},[67,4517,4518,4521,4523,4525,4528],{"class":69,"line":894},[67,4519,4520],{"class":104},"    person ",[67,4522,2586],{"class":163},[67,4524,2730],{"class":163},[67,4526,4527],{"class":73}," Person",[67,4529,2756],{"class":104},[67,4531,4532],{"class":69,"line":905},[67,4533,989],{"emptyLinePlaceholder":988},[67,4535,4536,4538,4540,4543,4546,4549,4551,4553],{"class":69,"line":916},[67,4537,3764],{"class":163},[67,4539,2556],{"class":104},[67,4541,4542],{"class":163},"!",[67,4544,4545],{"class":104},"person.",[67,4547,4548],{"class":73},"squeezesDuck",[67,4550,2556],{"class":104},[67,4552,3465],{"class":113},[67,4554,4555],{"class":104},")) {\n",[67,4557,4558,4561,4563,4566,4568,4571],{"class":69,"line":927},[67,4559,4560],{"class":163},"      throw",[67,4562,2730],{"class":163},[67,4564,4565],{"class":73}," Error",[67,4567,2556],{"class":104},[67,4569,4570],{"class":77},"'The rubber duck cannot swim on its own'",[67,4572,2745],{"class":104},[67,4574,4575],{"class":69,"line":935},[67,4576,1557],{"class":104},[67,4578,4579],{"class":69,"line":958},[67,4580,989],{"emptyLinePlaceholder":988},[67,4582,4583,4585,4587],{"class":69,"line":971},[67,4584,2674],{"class":163},[67,4586,4415],{"class":77},[67,4588,2485],{"class":104},[67,4590,4591],{"class":69,"line":985},[67,4592,1596],{"class":104},[67,4594,4595],{"class":69,"line":992},[67,4596,989],{"emptyLinePlaceholder":988},[67,4598,4599,4601],{"class":69,"line":1000},[67,4600,4430],{"class":73},[67,4602,2617],{"class":104},[67,4604,4605,4608,4610,4612,4614,4617],{"class":69,"line":1009},[67,4606,4607],{"class":163},"    throw",[67,4609,2730],{"class":163},[67,4611,4565],{"class":73},[67,4613,2556],{"class":104},[67,4615,4616],{"class":77},"'The rubber duck cannot fly'",[67,4618,2745],{"class":104},[67,4620,4621],{"class":69,"line":1016},[67,4622,1596],{"class":104},[67,4624,4625],{"class":69,"line":1025},[67,4626,989],{"emptyLinePlaceholder":988},[67,4628,4629,4631,4633,4635,4637],{"class":69,"line":1034},[67,4630,4460],{"class":73},[67,4632,2999],{"class":104},[67,4634,1390],{"class":163},[67,4636,2529],{"class":113},[67,4638,2500],{"class":104},[67,4640,4641,4643,4645,4647,4649],{"class":69,"line":1043},[67,4642,4520],{"class":104},[67,4644,2586],{"class":163},[67,4646,2730],{"class":163},[67,4648,4527],{"class":73},[67,4650,2756],{"class":104},[67,4652,4653],{"class":69,"line":1054},[67,4654,989],{"emptyLinePlaceholder":988},[67,4656,4657,4659,4661,4663,4665,4668,4670,4672],{"class":69,"line":1061},[67,4658,3764],{"class":163},[67,4660,2556],{"class":104},[67,4662,4542],{"class":163},[67,4664,4545],{"class":104},[67,4666,4667],{"class":73},"throwDuckInBath",[67,4669,2556],{"class":104},[67,4671,3465],{"class":113},[67,4673,4555],{"class":104},[67,4675,4676,4678,4680,4682,4684,4686],{"class":69,"line":1068},[67,4677,4560],{"class":163},[67,4679,2730],{"class":163},[67,4681,4565],{"class":73},[67,4683,2556],{"class":104},[67,4685,4570],{"class":77},[67,4687,2745],{"class":104},[67,4689,4690],{"class":69,"line":1075},[67,4691,1557],{"class":104},[67,4693,4694],{"class":69,"line":1085},[67,4695,989],{"emptyLinePlaceholder":988},[67,4697,4698,4700,4702],{"class":69,"line":1095},[67,4699,2674],{"class":163},[67,4701,4475],{"class":77},[67,4703,2485],{"class":104},[67,4705,4706],{"class":69,"line":1104},[67,4707,1596],{"class":104},[67,4709,4710],{"class":69,"line":3085},[67,4711,1601],{"class":104},[11,4713,4714,4715,4718,4719,4722,4723,3490,4726,4728,4729,4731,4732,4734],{},"Notice how we are changing every method of the ",[53,4716,4717],{},"abstract class Duck"," in our concrete class ",[53,4720,4721],{},"RubberDuck",". This is the first clue that we are violating LSP. The second clue is the ",[53,4724,4725],{},"fly",[53,4727,4721],{}," class. It just throws an Error. This means that if we have an instance of the ",[53,4730,4721],{}," somewhere within our app and we want to call the ",[53,4733,4725],{}," method on that instance our app will fail every time, which is a signal of a bad code design.",[11,4736,4737],{},"The way we can resolve this bad code design and follow LSP is coding by contract instead of extending an abstract class. This means that our class will implement only the contracts needed for its implementation.",[58,4739,4741],{"className":2464,"code":4740,"language":2466,"meta":63,"style":63},"interface QuackableInterface {\n  swim(): string;\n}\n\ninterface FlyableInterface {\n  fly(): string;\n}\n\ninterface SwimmableInterface {\n  swim(): string;\n}\n\nclass RubberDuck implements QuackableInterface, SwimmableInterface {\n  quack(): string {\n    person = new Person();\n\n    if (!person.squeezesDuck(this)) {\n      throw new Error(\"The rubber duck cannot swim on its own\");\n    }\n\n    return \"The duck is quacking\";\n  }\n\n  swim(): string {\n    person = new Person();\n\n    if (!person.throwDuckInBath(this)) {\n      throw new Error(\"The rubber duck cannot swim on its own\");\n    }\n\n    return \"The duck is swimming\";\n  }\n}\n",[53,4742,4743,4752,4764,4768,4772,4781,4793,4797,4801,4810,4822,4826,4830,4847,4859,4871,4875,4894,4909,4913,4917,4926,4930,4934,4946,4958,4962,4980,4994,4998,5002,5011,5015],{"__ignoreMap":63},[67,4744,4745,4747,4750],{"class":69,"line":70},[67,4746,3918],{"class":163},[67,4748,4749],{"class":73}," QuackableInterface",[67,4751,2500],{"class":104},[67,4753,4754,4756,4758,4760,4762],{"class":69,"line":251},[67,4755,4460],{"class":73},[67,4757,2999],{"class":104},[67,4759,1390],{"class":163},[67,4761,2529],{"class":113},[67,4763,2485],{"class":104},[67,4765,4766],{"class":69,"line":264},[67,4767,1601],{"class":104},[67,4769,4770],{"class":69,"line":280},[67,4771,989],{"emptyLinePlaceholder":988},[67,4773,4774,4776,4779],{"class":69,"line":288},[67,4775,3918],{"class":163},[67,4777,4778],{"class":73}," FlyableInterface",[67,4780,2500],{"class":104},[67,4782,4783,4785,4787,4789,4791],{"class":69,"line":781},[67,4784,4430],{"class":73},[67,4786,2999],{"class":104},[67,4788,1390],{"class":163},[67,4790,2529],{"class":113},[67,4792,2485],{"class":104},[67,4794,4795],{"class":69,"line":792},[67,4796,1601],{"class":104},[67,4798,4799],{"class":69,"line":803},[67,4800,989],{"emptyLinePlaceholder":988},[67,4802,4803,4805,4808],{"class":69,"line":814},[67,4804,3918],{"class":163},[67,4806,4807],{"class":73}," SwimmableInterface",[67,4809,2500],{"class":104},[67,4811,4812,4814,4816,4818,4820],{"class":69,"line":825},[67,4813,4460],{"class":73},[67,4815,2999],{"class":104},[67,4817,1390],{"class":163},[67,4819,2529],{"class":113},[67,4821,2485],{"class":104},[67,4823,4824],{"class":69,"line":836},[67,4825,1601],{"class":104},[67,4827,4828],{"class":69,"line":847},[67,4829,989],{"emptyLinePlaceholder":988},[67,4831,4832,4834,4836,4838,4840,4842,4845],{"class":69,"line":858},[67,4833,2494],{"class":163},[67,4835,4496],{"class":73},[67,4837,3956],{"class":163},[67,4839,4749],{"class":73},[67,4841,944],{"class":104},[67,4843,4844],{"class":73},"SwimmableInterface",[67,4846,2500],{"class":104},[67,4848,4849,4851,4853,4855,4857],{"class":69,"line":869},[67,4850,4400],{"class":73},[67,4852,2999],{"class":104},[67,4854,1390],{"class":163},[67,4856,2529],{"class":113},[67,4858,2500],{"class":104},[67,4860,4861,4863,4865,4867,4869],{"class":69,"line":877},[67,4862,4520],{"class":104},[67,4864,2586],{"class":163},[67,4866,2730],{"class":163},[67,4868,4527],{"class":73},[67,4870,2756],{"class":104},[67,4872,4873],{"class":69,"line":886},[67,4874,989],{"emptyLinePlaceholder":988},[67,4876,4877,4879,4882,4884,4886,4888,4890,4892],{"class":69,"line":894},[67,4878,3764],{"class":163},[67,4880,4881],{"class":104}," (",[67,4883,4542],{"class":163},[67,4885,4545],{"class":104},[67,4887,4548],{"class":73},[67,4889,2556],{"class":104},[67,4891,3465],{"class":113},[67,4893,4555],{"class":104},[67,4895,4896,4898,4900,4902,4904,4907],{"class":69,"line":905},[67,4897,4560],{"class":163},[67,4899,2730],{"class":163},[67,4901,4565],{"class":73},[67,4903,2556],{"class":104},[67,4905,4906],{"class":77},"\"The rubber duck cannot swim on its own\"",[67,4908,2745],{"class":104},[67,4910,4911],{"class":69,"line":916},[67,4912,1557],{"class":104},[67,4914,4915],{"class":69,"line":927},[67,4916,989],{"emptyLinePlaceholder":988},[67,4918,4919,4921,4924],{"class":69,"line":935},[67,4920,2674],{"class":163},[67,4922,4923],{"class":77}," \"The duck is quacking\"",[67,4925,2485],{"class":104},[67,4927,4928],{"class":69,"line":958},[67,4929,1596],{"class":104},[67,4931,4932],{"class":69,"line":971},[67,4933,989],{"emptyLinePlaceholder":988},[67,4935,4936,4938,4940,4942,4944],{"class":69,"line":985},[67,4937,4460],{"class":73},[67,4939,2999],{"class":104},[67,4941,1390],{"class":163},[67,4943,2529],{"class":113},[67,4945,2500],{"class":104},[67,4947,4948,4950,4952,4954,4956],{"class":69,"line":992},[67,4949,4520],{"class":104},[67,4951,2586],{"class":163},[67,4953,2730],{"class":163},[67,4955,4527],{"class":73},[67,4957,2756],{"class":104},[67,4959,4960],{"class":69,"line":1000},[67,4961,989],{"emptyLinePlaceholder":988},[67,4963,4964,4966,4968,4970,4972,4974,4976,4978],{"class":69,"line":1009},[67,4965,3764],{"class":163},[67,4967,4881],{"class":104},[67,4969,4542],{"class":163},[67,4971,4545],{"class":104},[67,4973,4667],{"class":73},[67,4975,2556],{"class":104},[67,4977,3465],{"class":113},[67,4979,4555],{"class":104},[67,4981,4982,4984,4986,4988,4990,4992],{"class":69,"line":1016},[67,4983,4560],{"class":163},[67,4985,2730],{"class":163},[67,4987,4565],{"class":73},[67,4989,2556],{"class":104},[67,4991,4906],{"class":77},[67,4993,2745],{"class":104},[67,4995,4996],{"class":69,"line":1025},[67,4997,1557],{"class":104},[67,4999,5000],{"class":69,"line":1034},[67,5001,989],{"emptyLinePlaceholder":988},[67,5003,5004,5006,5009],{"class":69,"line":1043},[67,5005,2674],{"class":163},[67,5007,5008],{"class":77}," \"The duck is swimming\"",[67,5010,2485],{"class":104},[67,5012,5013],{"class":69,"line":1054},[67,5014,1596],{"class":104},[67,5016,5017],{"class":69,"line":1061},[67,5018,1601],{"class":104},[1758,5020,5022],{"id":5021},"interface-segregation-principle","Interface Segregation Principle",[11,5024,5025,5026,5029],{},"It is quite common to find that an interface is in essence just a description of an entire class. ISP states that ",[21,5027,5028],{},"we should write a series of smaller and more specific interfaces"," that are implemented by the class. Each interface provides a single behavior. It's also important to mention that an instance (or a method) should never be dependent on methods it doesn't use.",[11,5031,5032],{},"Let's see through an example of what does this means.",[58,5034,5036],{"className":2464,"code":5035,"language":2466,"meta":63,"style":63},"class Subscriber {\n  subscribe() {\n    \u002F\u002F\n  }\n\n  unsubscribe() {\n    \u002F\u002F\n  }\n\n  getNotifyEmail() {\n    \u002F\u002F\n  }\n}\n\nclass Notification {\n  send(subscriber: Subscriber, message: string) {\n    \u002F\u002F imagine we already have some EmailClient service\n    const emailClient = new EmailClient();\n\n    emailClient.send(subscriber.getNotifyEmail(), message);\n  }\n}\n",[53,5037,5038,5047,5054,5059,5063,5067,5074,5078,5082,5086,5093,5097,5101,5105,5109,5118,5143,5148,5163,5167,5183,5187],{"__ignoreMap":63},[67,5039,5040,5042,5045],{"class":69,"line":70},[67,5041,2494],{"class":163},[67,5043,5044],{"class":73}," Subscriber",[67,5046,2500],{"class":104},[67,5048,5049,5052],{"class":69,"line":251},[67,5050,5051],{"class":73},"  subscribe",[67,5053,2617],{"class":104},[67,5055,5056],{"class":69,"line":264},[67,5057,5058],{"class":3401},"    \u002F\u002F\n",[67,5060,5061],{"class":69,"line":280},[67,5062,1596],{"class":104},[67,5064,5065],{"class":69,"line":288},[67,5066,989],{"emptyLinePlaceholder":988},[67,5068,5069,5072],{"class":69,"line":781},[67,5070,5071],{"class":73},"  unsubscribe",[67,5073,2617],{"class":104},[67,5075,5076],{"class":69,"line":792},[67,5077,5058],{"class":3401},[67,5079,5080],{"class":69,"line":803},[67,5081,1596],{"class":104},[67,5083,5084],{"class":69,"line":814},[67,5085,989],{"emptyLinePlaceholder":988},[67,5087,5088,5091],{"class":69,"line":825},[67,5089,5090],{"class":73},"  getNotifyEmail",[67,5092,2617],{"class":104},[67,5094,5095],{"class":69,"line":836},[67,5096,5058],{"class":3401},[67,5098,5099],{"class":69,"line":847},[67,5100,1596],{"class":104},[67,5102,5103],{"class":69,"line":858},[67,5104,1601],{"class":104},[67,5106,5107],{"class":69,"line":869},[67,5108,989],{"emptyLinePlaceholder":988},[67,5110,5111,5113,5116],{"class":69,"line":877},[67,5112,2494],{"class":163},[67,5114,5115],{"class":73}," Notification",[67,5117,2500],{"class":104},[67,5119,5120,5123,5125,5128,5130,5132,5134,5137,5139,5141],{"class":69,"line":886},[67,5121,5122],{"class":73},"  send",[67,5124,2556],{"class":104},[67,5126,5127],{"class":2508},"subscriber",[67,5129,1390],{"class":163},[67,5131,5044],{"class":73},[67,5133,944],{"class":104},[67,5135,5136],{"class":2508},"message",[67,5138,1390],{"class":163},[67,5140,2529],{"class":113},[67,5142,2575],{"class":104},[67,5144,5145],{"class":69,"line":894},[67,5146,5147],{"class":3401},"    \u002F\u002F imagine we already have some EmailClient service\n",[67,5149,5150,5152,5155,5157,5159,5161],{"class":69,"line":905},[67,5151,2622],{"class":163},[67,5153,5154],{"class":113}," emailClient",[67,5156,2512],{"class":163},[67,5158,2730],{"class":163},[67,5160,2794],{"class":73},[67,5162,2756],{"class":104},[67,5164,5165],{"class":69,"line":916},[67,5166,989],{"emptyLinePlaceholder":988},[67,5168,5169,5172,5174,5177,5180],{"class":69,"line":927},[67,5170,5171],{"class":104},"    emailClient.",[67,5173,3202],{"class":73},[67,5175,5176],{"class":104},"(subscriber.",[67,5178,5179],{"class":73},"getNotifyEmail",[67,5181,5182],{"class":104},"(), message);\n",[67,5184,5185],{"class":69,"line":935},[67,5186,1596],{"class":104},[67,5188,5189],{"class":69,"line":958},[67,5190,1601],{"class":104},[11,5192,5193,5194,5196,5197,5200,5201,5204,5205,5207,5208,5210],{},"Notice here that if the implementation of the ",[53,5195,5179],{}," in the ",[53,5198,5199],{},"Subscriber"," class changes, our ",[53,5202,5203],{},"Notification"," class will need to change as well even though they are not dependant on each other. To comply with the ISP, we can create an interface that implements the ",[53,5206,5179],{}," and have the ",[53,5209,5199],{}," class implement that interface:",[58,5212,5214],{"className":2464,"code":5213,"language":2466,"meta":63,"style":63},"interface NotifiableInterface {\n  getNotifyEmail(): string;\n}\n\nclass Subscriber implements NotifiableInterface {\n  subscribe() {\n    \u002F\u002F\n  }\n\n  unsubscribe() {\n    \u002F\u002F\n  }\n\n  getNotifyEmail() {\n    \u002F\u002F\n  }\n}\n\nclass Notification {\n  send(subscriber: NotifiableInterface, message: string) {\n    \u002F\u002F imagine we already have some EmailClient service\n    const emailClient = new EmailClient();\n\n    emailClient.send(subscriber.getNotifyEmail(), message);\n  }\n}\n",[53,5215,5216,5225,5237,5241,5245,5257,5263,5267,5271,5275,5281,5285,5289,5293,5299,5303,5307,5311,5315,5323,5345,5349,5363,5367,5379,5383],{"__ignoreMap":63},[67,5217,5218,5220,5223],{"class":69,"line":70},[67,5219,3918],{"class":163},[67,5221,5222],{"class":73}," NotifiableInterface",[67,5224,2500],{"class":104},[67,5226,5227,5229,5231,5233,5235],{"class":69,"line":251},[67,5228,5090],{"class":73},[67,5230,2999],{"class":104},[67,5232,1390],{"class":163},[67,5234,2529],{"class":113},[67,5236,2485],{"class":104},[67,5238,5239],{"class":69,"line":264},[67,5240,1601],{"class":104},[67,5242,5243],{"class":69,"line":280},[67,5244,989],{"emptyLinePlaceholder":988},[67,5246,5247,5249,5251,5253,5255],{"class":69,"line":288},[67,5248,2494],{"class":163},[67,5250,5044],{"class":73},[67,5252,3956],{"class":163},[67,5254,5222],{"class":73},[67,5256,2500],{"class":104},[67,5258,5259,5261],{"class":69,"line":781},[67,5260,5051],{"class":73},[67,5262,2617],{"class":104},[67,5264,5265],{"class":69,"line":792},[67,5266,5058],{"class":3401},[67,5268,5269],{"class":69,"line":803},[67,5270,1596],{"class":104},[67,5272,5273],{"class":69,"line":814},[67,5274,989],{"emptyLinePlaceholder":988},[67,5276,5277,5279],{"class":69,"line":825},[67,5278,5071],{"class":73},[67,5280,2617],{"class":104},[67,5282,5283],{"class":69,"line":836},[67,5284,5058],{"class":3401},[67,5286,5287],{"class":69,"line":847},[67,5288,1596],{"class":104},[67,5290,5291],{"class":69,"line":858},[67,5292,989],{"emptyLinePlaceholder":988},[67,5294,5295,5297],{"class":69,"line":869},[67,5296,5090],{"class":73},[67,5298,2617],{"class":104},[67,5300,5301],{"class":69,"line":877},[67,5302,5058],{"class":3401},[67,5304,5305],{"class":69,"line":886},[67,5306,1596],{"class":104},[67,5308,5309],{"class":69,"line":894},[67,5310,1601],{"class":104},[67,5312,5313],{"class":69,"line":905},[67,5314,989],{"emptyLinePlaceholder":988},[67,5316,5317,5319,5321],{"class":69,"line":916},[67,5318,2494],{"class":163},[67,5320,5115],{"class":73},[67,5322,2500],{"class":104},[67,5324,5325,5327,5329,5331,5333,5335,5337,5339,5341,5343],{"class":69,"line":927},[67,5326,5122],{"class":73},[67,5328,2556],{"class":104},[67,5330,5127],{"class":2508},[67,5332,1390],{"class":163},[67,5334,5222],{"class":73},[67,5336,944],{"class":104},[67,5338,5136],{"class":2508},[67,5340,1390],{"class":163},[67,5342,2529],{"class":113},[67,5344,2575],{"class":104},[67,5346,5347],{"class":69,"line":935},[67,5348,5147],{"class":3401},[67,5350,5351,5353,5355,5357,5359,5361],{"class":69,"line":958},[67,5352,2622],{"class":163},[67,5354,5154],{"class":113},[67,5356,2512],{"class":163},[67,5358,2730],{"class":163},[67,5360,2794],{"class":73},[67,5362,2756],{"class":104},[67,5364,5365],{"class":69,"line":971},[67,5366,989],{"emptyLinePlaceholder":988},[67,5368,5369,5371,5373,5375,5377],{"class":69,"line":985},[67,5370,5171],{"class":104},[67,5372,3202],{"class":73},[67,5374,5176],{"class":104},[67,5376,5179],{"class":73},[67,5378,5182],{"class":104},[67,5380,5381],{"class":69,"line":992},[67,5382,1596],{"class":104},[67,5384,5385],{"class":69,"line":1000},[67,5386,1601],{"class":104},[11,5388,5389,5390,3490,5392,5394,5395,5397,5398,5196,5400,5403],{},"Now, our ",[53,5391,3202],{},[53,5393,5203],{}," class doesn't depend on the whole ",[53,5396,5199],{}," method and only depends on the particular method ",[53,5399,5179],{},[53,5401,5402],{},"NotifiableInterface"," interface.",[1758,5405,5407],{"id":5406},"dependency-inversion-principle","Dependency Inversion Principle",[11,5409,5410,5411,5414],{},"This is the last one, I promise. The core of DIP is that ",[21,5412,5413],{},"high-level modules should not depend on the low-level modules. Instead, both of them should depend on abstractions."," This definition is way too abstract and technical, so let's break it down with examples and simplified explanations.",[11,5416,5417],{},"Let's see a real-life example of DIP. Let's say you want to charge your laptop. You can do that using your power supply adapter by plugging it into the wall socket. We don't dig a hole in the wall to find the wirings and plug the adapter directly into our power supply grid.",[11,5419,5420],{},"To translate the metaphor above coding-wise, our interface is the socket, and it provides us with the means (functions\u002Fclasses\u002Fmodules) to achieve the desired outcome. This means that we don't depend on a concrete implementation but we depend on abstractions. The most common example to take that implement DIP are database engines. You don't care what happens under the hood, what you care about is that the abstraction provided you enough functions to read and manipulate your data.",[11,5422,5423],{},"Other benefits that this principle offers:",[26,5425,5426,5429],{},[29,5427,5428],{},"Easy testing and clear boundaries of dependencies.",[29,5430,5431],{},"Low coupling, allowing for varying implementations or business requirements changing but the contract between modules remains the same.",[11,5433,5434,5435,5438,5439,5442],{},"Let's see this principle through an example. Let's build a ",[53,5436,5437],{},"SignupService"," that uses an ",[53,5440,5441],{},"HttpClient"," as a data source.",[58,5444,5446],{"className":2464,"code":5445,"language":2466,"meta":63,"style":63},"\u002F\u002F classes\u002FHttpClient.ts\nimport axios from \"axios\";\n\nexport default {\n  createUser: async (user: User) => {\n    return axios.post(\u002F* ... *\u002F);\n  },\n\n  getUserByEmail: async (email: string) => {\n    return axios.get(\u002F* ... *\u002F);\n  },\n};\n\n\u002F\u002F services\u002FSignupService.ts\nimport HttpClient from \"classes\u002FHttpClient\"; \u002F\u002F ❌ the domain depends on a concretion\n\nexport async function signup(email: string, password: string) {\n  const existingUser = await HttpClient.getUserByEmail(email);\n\n  if (existingUser) {\n    throw new Error(\"Email already used\");\n  }\n\n  return HttpClient.createUser({ email, password });\n}\n",[53,5447,5448,5453,5465,5469,5478,5503,5520,5524,5528,5551,5565,5569,5574,5578,5583,5601,5605,5637,5659,5663,5671,5686,5690,5694,5707],{"__ignoreMap":63},[67,5449,5450],{"class":69,"line":70},[67,5451,5452],{"class":3401},"\u002F\u002F classes\u002FHttpClient.ts\n",[67,5454,5455,5457,5459,5461,5463],{"class":69,"line":251},[67,5456,2473],{"class":163},[67,5458,2476],{"class":104},[67,5460,2479],{"class":163},[67,5462,2482],{"class":77},[67,5464,2485],{"class":104},[67,5466,5467],{"class":69,"line":264},[67,5468,989],{"emptyLinePlaceholder":988},[67,5470,5471,5474,5476],{"class":69,"line":280},[67,5472,5473],{"class":163},"export",[67,5475,242],{"class":163},[67,5477,2500],{"class":104},[67,5479,5480,5483,5485,5488,5490,5492,5494,5496,5498,5501],{"class":69,"line":288},[67,5481,5482],{"class":73},"  createUser",[67,5484,758],{"class":104},[67,5486,5487],{"class":163},"async",[67,5489,4881],{"class":104},[67,5491,3128],{"class":2508},[67,5493,1390],{"class":163},[67,5495,2497],{"class":73},[67,5497,4280],{"class":104},[67,5499,5500],{"class":163},"=>",[67,5502,2500],{"class":104},[67,5504,5505,5507,5510,5513,5515,5518],{"class":69,"line":781},[67,5506,2674],{"class":163},[67,5508,5509],{"class":104}," axios.",[67,5511,5512],{"class":73},"post",[67,5514,2556],{"class":104},[67,5516,5517],{"class":3401},"\u002F* ... *\u002F",[67,5519,2745],{"class":104},[67,5521,5522],{"class":69,"line":792},[67,5523,1562],{"class":104},[67,5525,5526],{"class":69,"line":803},[67,5527,989],{"emptyLinePlaceholder":988},[67,5529,5530,5533,5535,5537,5539,5541,5543,5545,5547,5549],{"class":69,"line":814},[67,5531,5532],{"class":73},"  getUserByEmail",[67,5534,758],{"class":104},[67,5536,5487],{"class":163},[67,5538,4881],{"class":104},[67,5540,2568],{"class":2508},[67,5542,1390],{"class":163},[67,5544,2529],{"class":113},[67,5546,4280],{"class":104},[67,5548,5500],{"class":163},[67,5550,2500],{"class":104},[67,5552,5553,5555,5557,5559,5561,5563],{"class":69,"line":825},[67,5554,2674],{"class":163},[67,5556,5509],{"class":104},[67,5558,3724],{"class":73},[67,5560,2556],{"class":104},[67,5562,5517],{"class":3401},[67,5564,2745],{"class":104},[67,5566,5567],{"class":69,"line":836},[67,5568,1562],{"class":104},[67,5570,5571],{"class":69,"line":847},[67,5572,5573],{"class":104},"};\n",[67,5575,5576],{"class":69,"line":858},[67,5577,989],{"emptyLinePlaceholder":988},[67,5579,5580],{"class":69,"line":869},[67,5581,5582],{"class":3401},"\u002F\u002F services\u002FSignupService.ts\n",[67,5584,5585,5587,5590,5592,5595,5598],{"class":69,"line":877},[67,5586,2473],{"class":163},[67,5588,5589],{"class":104}," HttpClient ",[67,5591,2479],{"class":163},[67,5593,5594],{"class":77}," \"classes\u002FHttpClient\"",[67,5596,5597],{"class":104},"; ",[67,5599,5600],{"class":3401},"\u002F\u002F ❌ the domain depends on a concretion\n",[67,5602,5603],{"class":69,"line":886},[67,5604,989],{"emptyLinePlaceholder":988},[67,5606,5607,5609,5612,5615,5618,5620,5622,5624,5626,5628,5631,5633,5635],{"class":69,"line":894},[67,5608,5473],{"class":163},[67,5610,5611],{"class":163}," async",[67,5613,5614],{"class":163}," function",[67,5616,5617],{"class":73}," signup",[67,5619,2556],{"class":104},[67,5621,2568],{"class":2508},[67,5623,1390],{"class":163},[67,5625,2529],{"class":113},[67,5627,944],{"class":104},[67,5629,5630],{"class":2508},"password",[67,5632,1390],{"class":163},[67,5634,2529],{"class":113},[67,5636,2575],{"class":104},[67,5638,5639,5642,5645,5647,5650,5653,5656],{"class":69,"line":905},[67,5640,5641],{"class":163},"  const",[67,5643,5644],{"class":113}," existingUser",[67,5646,2512],{"class":163},[67,5648,5649],{"class":163}," await",[67,5651,5652],{"class":104}," HttpClient.",[67,5654,5655],{"class":73},"getUserByEmail",[67,5657,5658],{"class":104},"(email);\n",[67,5660,5661],{"class":69,"line":916},[67,5662,989],{"emptyLinePlaceholder":988},[67,5664,5665,5668],{"class":69,"line":927},[67,5666,5667],{"class":163},"  if",[67,5669,5670],{"class":104}," (existingUser) {\n",[67,5672,5673,5675,5677,5679,5681,5684],{"class":69,"line":935},[67,5674,4607],{"class":163},[67,5676,2730],{"class":163},[67,5678,4565],{"class":73},[67,5680,2556],{"class":104},[67,5682,5683],{"class":77},"\"Email already used\"",[67,5685,2745],{"class":104},[67,5687,5688],{"class":69,"line":958},[67,5689,1596],{"class":104},[67,5691,5692],{"class":69,"line":971},[67,5693,989],{"emptyLinePlaceholder":988},[67,5695,5696,5699,5701,5704],{"class":69,"line":985},[67,5697,5698],{"class":163},"  return",[67,5700,5652],{"class":104},[67,5702,5703],{"class":73},"createUser",[67,5705,5706],{"class":104},"({ email, password });\n",[67,5708,5709],{"class":69,"line":992},[67,5710,1601],{"class":104},[11,5712,5713,5714],{},"This isn't ideal. We have just created a dependency from our domain to an implementation detail (HTTP) - crossing an architectural boundary and thus violating the Dependency rule. Furthermore, ",[21,5715,5716,5717,5720,5721,1819,5723,5725],{},"because ",[53,5718,5719],{},"signup"," is coupled with ",[53,5722,5441],{},[53,5724,5719],{}," it can't be unit tested.",[11,5727,5728,5729,2004,5731,56],{},"Let's reimplement it with dependency inversion this time! We're going to start by decoupling ",[53,5730,5437],{},[53,5732,5441],{},[58,5734,5736],{"className":2464,"code":5735,"language":2466,"meta":63,"style":63},"\u002F\u002F contracts\u002FApiClient.ts\nexport interface ApiClient {\n  createUser: (user: User) => Promise\u003Cvoid>;\n  getUserByEmail: (email: string) => Promise\u003CUser>;\n}\n\n\u002F\u002F classes\u002FHttpClient.ts\nimport axios from \"axios\";\nimport ApiClient from \"contracts\u002FApiClient\";\n\nexport function HttpClient(): ApiClient {\n  return {\n    createUser: async (user: User) => {\n      return axios.post(\u002F* ... *\u002F);\n    },\n    getUserByEmail: async (email: string) => {\n      return axios.get(\u002F* ... *\u002F);\n    },\n  };\n}\n\n\u002F\u002F services\u002FSignupService.ts\nimport ApiClient from \"contracts\u002FApiClient\"; \u002F\u002F ✅ the domain depends on an abstraction\n\nexport function SignupService(client: ApiClient) {\n  return async (email: string, password: string) => {\n    const existingUser = await client.getUserByEmail(email);\n\n    if (existingUser) {\n      throw new Error(\"Email already used\");\n    }\n\n    return client.createUser({ email, password });\n  };\n}\n",[53,5737,5738,5743,5755,5785,5811,5815,5819,5823,5835,5849,5853,5870,5876,5899,5913,5918,5941,5955,5959,5964,5968,5972,5976,5991,5995,6015,6043,6060,6064,6070,6084,6088,6092,6102,6106],{"__ignoreMap":63},[67,5739,5740],{"class":69,"line":70},[67,5741,5742],{"class":3401},"\u002F\u002F contracts\u002FApiClient.ts\n",[67,5744,5745,5747,5750,5753],{"class":69,"line":251},[67,5746,5473],{"class":163},[67,5748,5749],{"class":163}," interface",[67,5751,5752],{"class":73}," ApiClient",[67,5754,2500],{"class":104},[67,5756,5757,5759,5761,5763,5765,5767,5769,5771,5773,5776,5779,5782],{"class":69,"line":264},[67,5758,5482],{"class":73},[67,5760,1390],{"class":163},[67,5762,4881],{"class":104},[67,5764,3128],{"class":2508},[67,5766,1390],{"class":163},[67,5768,2497],{"class":73},[67,5770,4280],{"class":104},[67,5772,5500],{"class":163},[67,5774,5775],{"class":73}," Promise",[67,5777,5778],{"class":104},"\u003C",[67,5780,5781],{"class":113},"void",[67,5783,5784],{"class":104},">;\n",[67,5786,5787,5789,5791,5793,5795,5797,5799,5801,5803,5805,5807,5809],{"class":69,"line":280},[67,5788,5532],{"class":73},[67,5790,1390],{"class":163},[67,5792,4881],{"class":104},[67,5794,2568],{"class":2508},[67,5796,1390],{"class":163},[67,5798,2529],{"class":113},[67,5800,4280],{"class":104},[67,5802,5500],{"class":163},[67,5804,5775],{"class":73},[67,5806,5778],{"class":104},[67,5808,2762],{"class":73},[67,5810,5784],{"class":104},[67,5812,5813],{"class":69,"line":288},[67,5814,1601],{"class":104},[67,5816,5817],{"class":69,"line":781},[67,5818,989],{"emptyLinePlaceholder":988},[67,5820,5821],{"class":69,"line":792},[67,5822,5452],{"class":3401},[67,5824,5825,5827,5829,5831,5833],{"class":69,"line":803},[67,5826,2473],{"class":163},[67,5828,2476],{"class":104},[67,5830,2479],{"class":163},[67,5832,2482],{"class":77},[67,5834,2485],{"class":104},[67,5836,5837,5839,5842,5844,5847],{"class":69,"line":814},[67,5838,2473],{"class":163},[67,5840,5841],{"class":104}," ApiClient ",[67,5843,2479],{"class":163},[67,5845,5846],{"class":77}," \"contracts\u002FApiClient\"",[67,5848,2485],{"class":104},[67,5850,5851],{"class":69,"line":825},[67,5852,989],{"emptyLinePlaceholder":988},[67,5854,5855,5857,5859,5862,5864,5866,5868],{"class":69,"line":836},[67,5856,5473],{"class":163},[67,5858,5614],{"class":163},[67,5860,5861],{"class":73}," HttpClient",[67,5863,2999],{"class":104},[67,5865,1390],{"class":163},[67,5867,5752],{"class":73},[67,5869,2500],{"class":104},[67,5871,5872,5874],{"class":69,"line":847},[67,5873,5698],{"class":163},[67,5875,2500],{"class":104},[67,5877,5878,5881,5883,5885,5887,5889,5891,5893,5895,5897],{"class":69,"line":858},[67,5879,5880],{"class":73},"    createUser",[67,5882,758],{"class":104},[67,5884,5487],{"class":163},[67,5886,4881],{"class":104},[67,5888,3128],{"class":2508},[67,5890,1390],{"class":163},[67,5892,2497],{"class":73},[67,5894,4280],{"class":104},[67,5896,5500],{"class":163},[67,5898,2500],{"class":104},[67,5900,5901,5903,5905,5907,5909,5911],{"class":69,"line":869},[67,5902,4126],{"class":163},[67,5904,5509],{"class":104},[67,5906,5512],{"class":73},[67,5908,2556],{"class":104},[67,5910,5517],{"class":3401},[67,5912,2745],{"class":104},[67,5914,5915],{"class":69,"line":877},[67,5916,5917],{"class":104},"    },\n",[67,5919,5920,5923,5925,5927,5929,5931,5933,5935,5937,5939],{"class":69,"line":886},[67,5921,5922],{"class":73},"    getUserByEmail",[67,5924,758],{"class":104},[67,5926,5487],{"class":163},[67,5928,4881],{"class":104},[67,5930,2568],{"class":2508},[67,5932,1390],{"class":163},[67,5934,2529],{"class":113},[67,5936,4280],{"class":104},[67,5938,5500],{"class":163},[67,5940,2500],{"class":104},[67,5942,5943,5945,5947,5949,5951,5953],{"class":69,"line":894},[67,5944,4126],{"class":163},[67,5946,5509],{"class":104},[67,5948,3724],{"class":73},[67,5950,2556],{"class":104},[67,5952,5517],{"class":3401},[67,5954,2745],{"class":104},[67,5956,5957],{"class":69,"line":905},[67,5958,5917],{"class":104},[67,5960,5961],{"class":69,"line":916},[67,5962,5963],{"class":104},"  };\n",[67,5965,5966],{"class":69,"line":927},[67,5967,1601],{"class":104},[67,5969,5970],{"class":69,"line":935},[67,5971,989],{"emptyLinePlaceholder":988},[67,5973,5974],{"class":69,"line":958},[67,5975,5582],{"class":3401},[67,5977,5978,5980,5982,5984,5986,5988],{"class":69,"line":971},[67,5979,2473],{"class":163},[67,5981,5841],{"class":104},[67,5983,2479],{"class":163},[67,5985,5846],{"class":77},[67,5987,5597],{"class":104},[67,5989,5990],{"class":3401},"\u002F\u002F ✅ the domain depends on an abstraction\n",[67,5992,5993],{"class":69,"line":985},[67,5994,989],{"emptyLinePlaceholder":988},[67,5996,5997,5999,6001,6004,6006,6009,6011,6013],{"class":69,"line":992},[67,5998,5473],{"class":163},[67,6000,5614],{"class":163},[67,6002,6003],{"class":73}," SignupService",[67,6005,2556],{"class":104},[67,6007,6008],{"class":2508},"client",[67,6010,1390],{"class":163},[67,6012,5752],{"class":73},[67,6014,2575],{"class":104},[67,6016,6017,6019,6021,6023,6025,6027,6029,6031,6033,6035,6037,6039,6041],{"class":69,"line":1000},[67,6018,5698],{"class":163},[67,6020,5611],{"class":163},[67,6022,4881],{"class":104},[67,6024,2568],{"class":2508},[67,6026,1390],{"class":163},[67,6028,2529],{"class":113},[67,6030,944],{"class":104},[67,6032,5630],{"class":2508},[67,6034,1390],{"class":163},[67,6036,2529],{"class":113},[67,6038,4280],{"class":104},[67,6040,5500],{"class":163},[67,6042,2500],{"class":104},[67,6044,6045,6047,6049,6051,6053,6056,6058],{"class":69,"line":1009},[67,6046,2622],{"class":163},[67,6048,5644],{"class":113},[67,6050,2512],{"class":163},[67,6052,5649],{"class":163},[67,6054,6055],{"class":104}," client.",[67,6057,5655],{"class":73},[67,6059,5658],{"class":104},[67,6061,6062],{"class":69,"line":1016},[67,6063,989],{"emptyLinePlaceholder":988},[67,6065,6066,6068],{"class":69,"line":1025},[67,6067,3764],{"class":163},[67,6069,5670],{"class":104},[67,6071,6072,6074,6076,6078,6080,6082],{"class":69,"line":1034},[67,6073,4560],{"class":163},[67,6075,2730],{"class":163},[67,6077,4565],{"class":73},[67,6079,2556],{"class":104},[67,6081,5683],{"class":77},[67,6083,2745],{"class":104},[67,6085,6086],{"class":69,"line":1043},[67,6087,1557],{"class":104},[67,6089,6090],{"class":69,"line":1054},[67,6091,989],{"emptyLinePlaceholder":988},[67,6093,6094,6096,6098,6100],{"class":69,"line":1061},[67,6095,2674],{"class":163},[67,6097,6055],{"class":104},[67,6099,5703],{"class":73},[67,6101,5706],{"class":104},[67,6103,6104],{"class":69,"line":1068},[67,6105,5963],{"class":104},[67,6107,6108],{"class":69,"line":1075},[67,6109,1601],{"class":104},[11,6111,6112,6113,6115,6116,6119,6120,6122],{},"With the power of dependency inversion, our ",[53,6114,5437],{}," can now use any ",[53,6117,6118],{},"ApiClient",". Let's inject our ",[53,6121,5441],{}," for now.",[58,6124,6126],{"className":2464,"code":6125,"language":2466,"meta":63,"style":63},"\u002F\u002F index.ts\nimport SignupService from \"services\u002Fsignup\";\nimport HttpClient from \"classes\u002FHttpClient\";\n\nconst signup = SignupService(HttpClient());\n\nsignup(\"bojan@example.com\", \"password\");\n",[53,6127,6128,6133,6147,6159,6163,6180,6184],{"__ignoreMap":63},[67,6129,6130],{"class":69,"line":70},[67,6131,6132],{"class":3401},"\u002F\u002F index.ts\n",[67,6134,6135,6137,6140,6142,6145],{"class":69,"line":251},[67,6136,2473],{"class":163},[67,6138,6139],{"class":104}," SignupService ",[67,6141,2479],{"class":163},[67,6143,6144],{"class":77}," \"services\u002Fsignup\"",[67,6146,2485],{"class":104},[67,6148,6149,6151,6153,6155,6157],{"class":69,"line":264},[67,6150,2473],{"class":163},[67,6152,5589],{"class":104},[67,6154,2479],{"class":163},[67,6156,5594],{"class":77},[67,6158,2485],{"class":104},[67,6160,6161],{"class":69,"line":280},[67,6162,989],{"emptyLinePlaceholder":988},[67,6164,6165,6167,6169,6171,6173,6175,6177],{"class":69,"line":288},[67,6166,2722],{"class":163},[67,6168,5617],{"class":113},[67,6170,2512],{"class":163},[67,6172,6003],{"class":73},[67,6174,2556],{"class":104},[67,6176,5441],{"class":73},[67,6178,6179],{"class":104},"());\n",[67,6181,6182],{"class":69,"line":781},[67,6183,989],{"emptyLinePlaceholder":988},[67,6185,6186,6188,6190,6193,6195,6198],{"class":69,"line":792},[67,6187,5719],{"class":73},[67,6189,2556],{"class":104},[67,6191,6192],{"class":77},"\"bojan@example.com\"",[67,6194,944],{"class":104},[67,6196,6197],{"class":77},"\"password\"",[67,6199,2745],{"class":104},[6201,6202],"hr",{},[11,6204,6205],{},"And there you have it. I know it has been a long article, but I wanted to share my knowledge about this particular topic for a long time.",[11,6207,6208],{},"Wish everyone a happy, productive, successful and healthy New Year & Merry Christmas!",[1670,6210,6211],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":63,"searchDepth":251,"depth":251,"links":6213},[6214,6215,6216],{"id":2363,"depth":251,"text":2364},{"id":2375,"depth":251,"text":2376},{"id":2395,"depth":251,"text":2396,"children":6217},[6218,6219,6220,6221,6222],{"id":2451,"depth":264,"text":2452},{"id":3336,"depth":264,"text":3337},{"id":4349,"depth":264,"text":4350},{"id":5021,"depth":264,"text":5022},{"id":5406,"depth":264,"text":5407},"TypeScript","2020\u002F12\u002F26","Yes, another take into explaining SOLID Design Principles. And yes, another blog post about making your code more maintainable and readable. But this time something is different - well, mostly nothing really is (you expected something here, didn't ya?) except that it's just my perspective. A perspective gained from applying these principles in real life projects.","\u002Fimages\u002Fsolid-ts.jpg","typescript, design patters, solid, solid design principles, single responsibility principle, open close principle, liskov substitution principle, dependency inversion principle, interface segregation principle",{},"\u002Fblog\u002Funderstand-and-use-solid-design-principles-with-typescript","☕️☕️ 19 min read",{"title":2351,"description":6225},"understand-and-use-solid-design-principles-with-typescript","blog\u002Funderstand-and-use-solid-design-principles-with-typescript","solid, typescript, design patterns","exK7fI-uA5IXl6sv5uvrn9lmCvwjMYdH-j6KdtUp8GU",1774945271910]