Install it from Chrome Web Store or build it from source
- Getting Started
- System Design
- Testing
- Deployment
- Tools We Use
- Contributing
- Code Review Guideline
- Author
- License
git clone https://github.com/byliuyang/short.git
- Go v1.13.1
- Node.js v12.12.0
- Yarn v1.19.1
- Postgresql v12.0 ( or use ElephantSQL instead )
-
Sign up at ReCAPTCHA with the following configurations:
Field Value Label Short
reCAPTCHA type reCAPTCHAv3
Domains localhost
-
Open
settings
. CopySITE KEY
andSECRET KEY
. -
Replace the value of
RECAPTCHA_SECRET
in thebackend/.env
file withSECRET KEY
. -
Replace the value of
REACT_APP_RECAPTCHA_SITE_KEY
infrontend/.env.development
file withSITE KEY
.
-
Create a new OAuth app at Github Developers with the following configurations:
Field Value Application Name Short
Homepage URL http://localhost
Application description URL shortening service written in Go and React
Authorization callback URL http://localhost/oauth/github/sign-in/callback
-
Copy
Client ID
andClient Secret
. -
Replace the value of
GITHUB_CLIENT_ID
in thebackend/.env
file withClient ID
. -
Replace the value of
GITHUB_CLIENT_SECRET
in thebackend/.env
file withClient Secret
.
-
Create a new app at Facebook Developers with the following configurations:
Field Value Display Name Short Test
Contact Email your_email -
Add
Facebook Login
to the app. -
Copy
App ID
andApp Secret
onSettings
>Basic
tab. -
Replace the value of
FACEBOOK_CLIENT_ID
inbackend/.env
file withApp ID
. -
Replace the value of
FACEBOOK_CLIENT_SECRET
inbackend/.env
file withApp Secret
.
-
Copy
backend/.env.dist
file tobackend/.env
:cp backend/.env.dist backend/.env
-
Update
DB_HOST
,DB_PORT
,DB_USER
,DB_PASSWORD
,DB_NAME
,RECAPTCHA_SECRET
,GITHUB_CLIENT_ID
,GITHUB_CLIENT_SECRET
,FACEBOOK_CLIENT_ID
,FACEBOOK_CLIENT_SECRET
,FACEBOOK_REDIRECT_URI
,JWT_SECRET
, with your own configurations. -
Launch backend server
cd backend ./scripts/dev
-
Remember to install developers tools before start coding:
./scripts/tools
Remember to update REACT_APP_RECAPTCHA_SITE_KEY
in frontend/.env.development
.
-
Launch frontend server
cd frontend ./scripts/dev
-
Visit http://localhost:3000
Short backend is built on top of Uncle Bob's Clean Architecture, the central objective of which is separation of concerns.
It enables the developers to modify a single component of the system at a time while leaving the rest unchanged. This minizes the amount of changes have to be made in order to support new requirements as the system grows. Clean Architecture also improves the testability of system, which in turn saves precious time when creating automated tests.
Here is an exmample of finance app using clean architecture:
Short adopts Microservices Architecture to organize dependent services around business capabilities and to enable independent deployment of each service.
Short leverages class design, package cohesion, and package coupling princiapls to manage logical dependency between internal components.
Principal | Description |
---|---|
Single Responsibility Principle | A class should have one, and only one, reason to change. |
Open Closed Principle | You should be able to extend a classes behavior, without modifying it. |
Liskov Substitution Principle | Derived classes must be substitutable for their base classes. |
Interface Segregation Principle | Make fine grained interfaces that are client specific. |
Dependency Inversion Principle | Depend on abstractions, not on concretions. |
Principal | Description |
---|---|
Release Reuse Equivalency Principle | The granule of reuse is the granule of release. |
The Common Closure Principle | Classes that change together are packaged together. |
The Common Reuse Principle | Classes that are used together are packaged together. |
Principal | Description |
---|---|
Acyclic Dependencies Principle | The dependency graph of packages must have no cycles. |
Stable Dependencies Principle | Depend in the direction of stability. |
Stable Abstractions Principle | Abstractness increases with stability. |
Short produces flexible and loosely coupled code, by explicitly providing components with all of the dependencies they need.
type Authenticator struct {
tokenizer fw.CryptoTokenizer
timer fw.Timer
tokenValidDuration time.Duration
}
func NewAuthenticator(
tokenizer fw.CryptoTokenizer,
timer fw.Timer,
tokenValidDuration time.Duration,
) Authenticator {
return Authenticator{
tokenizer: tokenizer,
timer: timer,
tokenValidDuration: tokenValidDuration,
}
}
Short also simplifies the management of the big block of order-dependent initialization code with Wire, a compile time depedency injection framework by Google.
func InjectGraphQlService(
name string,
sqlDB *sql.DB,
graphqlPath provider.GraphQlPath,
secret provider.ReCaptchaSecret,
jwtSecret provider.JwtSecret,
bufferSize provider.KeyGenBufferSize,
kgsRPCConfig provider.KgsRPCConfig,
) (mdservice.Service, error) {
wire.Build(
wire.Bind(new(fw.GraphQlAPI), new(graphql.Short)),
wire.Bind(new(url.Retriever), new(url.RetrieverPersist)),
wire.Bind(new(url.Creator), new(url.CreatorPersist)),
wire.Bind(new(repo.UserURLRelation), new(db.UserURLRelationSQL)),
wire.Bind(new(repo.URL), new(*db.URLSql)),
wire.Bind(new(keygen.KeyGenerator), new(keygen.Remote)),
wire.Bind(new(service.KeyFetcher), new(kgs.RPC)),
observabilitySet,
authSet,
mdservice.New,
provider.NewGraphGophers,
mdhttp.NewClient,
mdrequest.NewHTTP,
mdtimer.NewTimer,
db.NewURLSql,
db.NewUserURLRelationSQL,
provider.NewRemote,
url.NewRetrieverPersist,
url.NewCreatorPersist,
provider.NewKgsRPC,
provider.NewReCaptchaService,
requester.NewVerifier,
graphql.NewShort,
)
return mdservice.Service{}, nil
}
Short employs feature toggles
to modify system behavior without changing code.
UI components controlled by the feature toggles are created inside a centralized
UIFactory
in order to avoid having nested if
else
statement across the
code base:
// UIFactory.tsx
export class UIFactory {
constructor(
private featureDecisionService: IFeatureDecisionService
) {}
public createGoogleSignInButton(): ReactElement {
if (!this.featureDecisionService.includeGoogleSignButton()) {
return <div />;
}
return (
<GoogleSignInButton
googleSignInLink={this.authService.googleSignInLink()}
/>
);
}
public createGithubSignInButton(): ReactElement {
if (!this.featureDecisionService.includeGithubSignButton()) {
return <div />;
}
return (
<GithubSignInButton
githubSignInLink={this.authService.githubSignInLink()}
/>
);
}
}
Short also provides IFeatureDecisionService
interface, allowing the developers
to switch to dynamic feature toggle backend in the future by simply swapping
the dependency injected.
// FeatureDecision.service.ts
export interface IFeatureDecisionService {
includeGithubSignButton(): boolean;
includeGoogleSignButton(): boolean;
includeFacebookSignButton(): boolean;
}
// StaticConfigDecision.service.ts
import { IFeatureDecisionService } from './FeatureDecision.service';
export class StaticConfigDecisionService implements IFeatureDecisionService {
includeGithubSignButton(): boolean {
return false;
}
includeGoogleSignButton(): boolean {
return false;
}
includeFacebookSignButton(): boolean {
return true;
}
}
// dep.ts
export function initUIFactory(
...
): UIFactory {
...
const staticConfigDecision = new StaticConfigDecisionService();
...
return new UIFactory(
...,
staticConfigDecision
);
}
You can read about the detailed feature toggle design on this article.
In order to improve the quality and quantity of the website's traffic, Short increases its visibility to web search engines through HTML meta tags.
<!-- ./frontend/public/index.html -->
<title>Short: Free online link shortening service</title>
<!-- Search Engine Optimization -->
<meta name="description"
content="Short enables people to type less for their favorite web sites">
<meta name="robots" content="index, follow">
<link href="https://short-d.com" rel="canonical">
If you search short-d.com
on Google, you should see Short shows up as
the first result:
Short leverages Open Graph
tags to control what content shows up in
the summary card when the website is shared on Facebook or LinkedIn:
<!-- ./frontend/public/index.html -->
<!-- Open Graph -->
<meta property="og:title" content="Short: Free link shortening service"/>
<meta property="og:description"
content="Short enables people to type less for their favorite web sites"/>
<meta property="og:image"
content="https://short-d.com/promo/small-tile.png"/>
<meta property="og:url" content="https://short-d.com"/>
<meta property="og:type" content="website"/>
Shared on Facebook:
Shared on LinkedIn:
Twitter uses its own meta tags to determine what will show up when the website is mentioned in a Tweet:
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:site" content="@byliuyang11"/>
<meta name="twitter:title" content="Short: Free link shortening service"/>
<meta name="twitter:description"
content="Short enables people to type less for their favorite web sites"/>
<meta name="twitter:image" content="https://short-d.com/promo/twitter-card.png"/>
Short is maintained by a small team of talented software engineers working at Google, Uber, and Vmware as a side project. The team wants to deliver new features faster without sacrificing its quality. Testing ever-increasing amount of features manually soon becomes impossible — unless we want to spend all our time with manual, repetitive work instead of delivering working features.
Test automation is the only way forward.
Please read Testing Strategies in a Microservice Architecture for a detailed introduction on test strategies.
A unit test exercises the smallest piece of testable software in the application to determine whether it behaves as expected.
- [F]ast: Unit tests should be fast otherwise they will slow down development & deployment.
- [I]ndependent: Never ever write tests which depend on other test cases.
- [R]epeatable: A repeatable test is one that produces the same results each time you run it.
- [S]elf-validating: There must be no manual interpretation of the results.
- [T]imely/[T]horoughly: Unit tests must be included for every pull request of a new feature and cover edge cases, errors, and bad inputs.
A automated test method should be composed of 3As: Arrange, Act, and Assert.
- [A]rrange: All the data needed for a test should be arranged as part of the test. The data used in a test should not depend on the environment in which the test is running.
- [A]ct: Invoke the actual method under test.
- [A]ssert: A test method should test for a single logical outcome.
An integration test verifies the communication paths and interactions between components to detect interface defects.
A component test limits the scope of the exercised software to a portion of the system under test, manipulating the system through internal code interfaces and using test doubles to isolate the code under test from other components.
An integration contract test is a test at the boundary of an external service verifying that it meets the contract expected by a consuming service.
An end-to-end test verifies that a system meets external requirements and achieves its goals, testing the entire system, from end to end.
Short leverages Kubernetes to automate deployment, scaling, and management of containerized microservices.
Short uses GitOps to manage Kubernetes cluster.
Merging pull request into master branch on Github will automatically deploy the changes to staging environment.
Merging from master
branch to production
branch will automatically
deploy the latest code to the production. This is called continuous
delivery in the DevOps world.
In the future, when there are enough automated tests, we may migrate to continuous deployment instead.
- Drone: Continuous integration written in Go
- Code Climate: Automated code review
- ElephantSQL: Managed PostgreSQL service.
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
- The code is well-designed.
- The functionality is good for the users of the code.
- Any UI changes are sensible and look good.
- Any parallel programming is done safely.
- The code isn’t more complex than it needs to be.
- The developer isn’t implementing things they might need in the future but don’t know they need now.
- Code has appropriate unit tests.
- Tests are well-designed.
- The developer used clear names for everything.
- Comments are clear and useful, and mostly explain why instead of what.
- Code is appropriately documented.
- Reviewers should favor approving a PR once it is in a state where it definitely improves the overall code health of the system being worked on, even if the PR isn’t perfect.
- Instead of seeking perfection, what a reviewer should seek is continuous improvement.
- If a PR adds a feature that the reviewer doesn’t want in their system, then the reviewer can certainly deny approval even if the code is well-designed.
- Reviewers should always feel free to leave comments expressing that something could be better, but if it’s not very important, prefix it with something like “Nit: “ to let the author know that it’s just a point of polish that they could choose to ignore.
- Checking in PRs that definitely worsen the overall code health of the system is not justified unless there is an emergency.
Code review can have an important function of teaching developers something new about a language, a framework, or general software design principles.
If the comment is purely educational, but not critical to meeting the standards above, prefix it with "Nit: ".
- Technical facts and data overrule opinions and personal preferences.
- On matters of style, the linters are the absolute authorities. The style should be consistent with what is there. If there is no previous style, accept the author’s.
- Aspects of software design are NOT personal preferences. Sometimes there are a few valid options. If the author can demonstrate that several approaches are equally valid, then the reviewer should accept the preference of the author. Otherwise the choice is dictated by standard principles of software design.
- If no other rule applies, then the reviewer may ask the author to be consistent with what is in the current codebase, as long as that doesn’t worsen the overall code health of the system.
If you see something nice in the PR, tell the developer, especially when they addressed one of your comments in a great way. Code reviews should offer encouragement and appreciation for good practices, as well. It’s sometimes even more valuable, in terms of mentoring, to tell a developer what they did right than to tell them what they did wrong.
When coming to consensus becomes especially difficult, it can help to have a face-to-face meeting or a video call between the reviewer and the author, instead of just trying to resolve the conflict through code review comments. (If you do this, though, make sure to record the results of the discussion in a comment on the CL, for future readers.)
Don’t let a PR sit around because the author and the reviewer can’t come to an agreement.
Note: This guideline is derived from Google Engineering Practices Documentation
Harry Liu - Initial work - byliuyang
As the tech lead of Short, I am responsible for the overall planning, execution and success of complex software solutions to meet users' needs.
I deeply believe in and am striving to achieve the right column of the following diagram:
This project is maintained under MIT license