API REST com princípios do SOLID e alguns patterns com o objetivo de simular uma aplicação escalável e de fácil manutenção.
- TypeScript - JavaScript com tipagem estática
- Node.js - interpretador do JavaScript em server-side
- Fastify - framework para construção do servidor
- Prisma - ORM
- Vitest - framework de testes
Implementação em baixo nível das funcionalidades do app com o princípio da inversão de dependência.
export class GetOrgProfileUseCase {
constructor(private orgsRepository: OrgsRepository) {}
async execute({
orgId,
}: GetOrgProfileUseCaseRequest): Promise<GetOrgProfileUseCaseResponse> {
const org = await this.orgsRepository.findById(orgId);
if (!org) throw new ResourceNotFoundError("org");
return { org: serializeUser(org) };
}
}
Isso permite que o caso de uso seja flexível e facilita a implementação de alguns patterns como mostrado a seguir.
Abstração de toda operação feita no banco de dados seguindo o princípio da responsabilidade única.
Cada repositório implementa uma interface em forma de contrato que a mesma deve seguir:
export interface OrgsRepository {
create(data: CreateOrg): Promise<Org>;
findById(id: string): Promise<Org | null>;
findByEmail(email: string): Promise<Org | null>;
findManyByCity(city: string): Promise<Org[]>;
}
Isso permite principalmente seguir um padrão ao escrever um repositório e não deixa o aplicativo dependente de framework ou banco de dados.
Utilizando Repository Pattern, basta criar novos repositórios implementando a interface base da entidade para garantir que o banco de dados em memória seja o mais próximo possível do banco de dados que será utilizado em produção.
Como os casos de uso seguem a inversão de dependência, basta utilizar os repositórios em memória ao instanciar os casos de uso nos testes unitários. Assim, é possível limpar o banco de dados antes de cada teste para se abster de contextos sem abrir mão da performance dos testes.
let orgsRepository: InMemoryOrgsRepository;
let sut: GetOrgProfileUseCase;
let orgId: string;
describe("Get org profile Use Case", () => {
beforeEach(async () => {
// instância do caso de uso com o repositório em memória
orgsRepository = new InMemoryOrgsRepository();
sut = new GetOrgProfileUseCase(orgsRepository);
const org = await orgsRepository.create({
...orgSpec,
passwordHash: orgPasswordHashSpec,
});
orgId = org.id;
});
it("should be able get the org profile", async () => {
const { org } = await sut.execute({ orgId });
expect(org.id).toEqual(orgId);
});
});
Defina as variáveis de ambiente:
cp env.example .env
Instale as dependências:
pnpm install
Inicie o banco de dados:
docker compose up -d && pnpm prisma migrate dev
Rode o projeto:
# inicie o servidor em ambiente dev (você pode ver a documentação da API em "routes.http")
pnpm start:dev
# rode os testes unitários
pnpm test
# rode os testes end to end
pnpm test:e2e