Минимализм с помощью Node

Ссылки

Прежде чем я начну это краткое изложение «Дао нода» Александра Кондова, на случай, если вам нужен первоисточник большей части того, о чем я говорю, они следуют здесь:

  • Дао узла
  • Что такое петля событий? — Филип Робертс
  • Внутри цикла событий — Джейк Арчибальд

С помощью этих ссылок, я думаю, у нас будет некоторая основа для того, о чем мы будем говорить сегодня в этом блоге.
В качестве образца я использовал вот этот проект

В начале

Независимо от того, какой проект вы будете делать в node, давайте поговорим немного о философии, в node у нас есть идея, что малое прекрасно, только то, что необходимо. Минимализм. Что это дает? У нас есть небольшие пакеты или модули, которые делают что-то очень хорошо и, вероятно, поддерживаются сообществом. Да, NPM или Yarn — это то, что является частью философии Node, и его пакеты несут это с собой. Express является самым большим примером и, в свою очередь, почти синонимом node, TypeScript — буквально JavaScript со специями — также очень хорошо принят… React и многие другие — это просто JS со специями, но очень хорошо сделанными специями.

Настройка

Очевидно, что при создании проекта в 2022 году, мы будем использовать TypeScript, который является решением для работы с увеличением нашей кодовой базы, мы также будем использовать fastify, больше по собственному желанию, поскольку нам нравится их философия и некоторые вещи из коробки, но express по-прежнему является отличным фреймворком/либом node.

Я также хотел бы отметить, что по предпочтениям я использую MongoDB, но это больше относится к тому, как будет храниться информация, чем к тому, как структурирован ваш код.
Каждая модель или домен приложения должны иметь свой собственный каталог и следовать туда со своими сложностями, оставляя самую простую и легкую визуализацию. В примере мы имеем только два домена в нашем приложении petshop — Pets и Customers:

Контроллеры

Когда мы говорим о контроллерах, они являются нашим фронт-эндом, где фронт-энд обращается, спрашивает или просто касается, это наш API. Когда вы думаете об API, он должен быть простым, но в то же время эффективно выполнять свою работу, делать то, что вам нужно. В таком виде мой фронтенд Customer выглядит следующим образом:

export async function CustomerController(fastify: FastifyInstance) {


    const customerService = CustomerService(fastify);
    const petService = PetService(fastify);

    fastify.get<{ Reply: Array<CustomerSchema> }>
    ('/customers',
        async (
            request: FastifyRequest, reply: FastifyReply
        ) => {
            const result = await customerService.getAllCustomers()
            if (result.length === 0) {
                reply.status(404);
                throw new Error('No documents found')
            }
            reply.status(200).send(result);
        });

    fastify.get<{ Params: { customerID: string }, Reply: CustomerSchema }>
    ('/customers/:customerID',
        async (
            request: FastifyRequest<{ Params: { customerID: string } }>,
            reply: FastifyReply
        ) => {
            const {customerID} = request.params;
            const result = await customerService.getCustomerById(customerID);
            if (!result) {
                reply.status(404).send(customerID);
                throw new Error('Invalid value');
            }
            reply.status(200).send(result);
        });

    fastify.get<{ Params: { customerID: string }, Reply: CustomerSchema }>
    ('/customers/:customerID/pets',
        async (
            request: FastifyRequest<{ Params: { customerID: string } }>,
            reply: FastifyReply
        ) => {
            const {customerID} = request.params;
            const customer = await customerService.getCustomerById(customerID);

            if (!customer) {
                reply.status(404).send('Invalid user id');
                throw new Error('Invalid user id');
            }

            if (customer.pets === undefined || customer.pets?.length === 0) {
                reply.status(400).send('No pets were added');
                throw new Error('No pets were added');
            }

            const res = await petService.getPetsByIds(customer.pets).toArray();

            if (res === null) {
                reply.status(500).send('DB broke');
                throw new Error('Something is wrong');
            }
            reply.status(200).send(res);
        });

    fastify.put<{ Body: CustomerSchema, Reply: CustomerSchema, Params: { customerID: string } }>
    ('/customers/:customerID',
        async (
            request: FastifyRequest<{ Body: CustomerSchema, Params: { customerID: string } }>,
            reply: FastifyReply
        ) => {
            const {customerID} = request.params;
            const customer = request.body;
            const result = await customerService.updateCustomer(customerID, customer);
            if (result.ok === 0) {
                reply.status(400).send(customer);
            }
            reply.status(200).send(customer);
        });

    fastify.post<{ Body: CustomerSchema, Reply: CustomerSchema }>
    ('/customers',
        async (
            request: FastifyRequest<{ Body: CustomerSchema, Reply: CustomerSchema }>,
            reply: FastifyReply
        ) => {
            const customer = request.body;
            const createdCustomer = await customerService.createCustomer(customer);
            reply.status(200).send(createdCustomer);
        });
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Видя этот контроллер, мы можем сделать некоторые выводы, отличающиеся, но очень похожие на проект на объектно-ориентированном языке, у нас есть инъекция зависимостей в начале, когда мы вызываем два сервиса, и каждый контроллер происходит в контексте Function.

Единственная обязанность контроллера — управлять потоком, вызывать функции и возвращать ошибки или данные, не обращаясь к бизнес-правилам / базе данных.

Давайте проследим за порядком логических частей кода, далее мы поговорим о сервисе и о том, за что он должен отвечать.

Услуги

Когда мы говорим о сервисах, думайте в двух частях, кто вызывает базу данных или контекст и работает с бизнес-правилами. В случае простого проекта, подобного этому, служба вызывает БД и обрабатывает только записи.

export default function PetService(
    fastify: FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance>
) {
    const db = PetContext(fastify);

    const getAllPets = () => {
        return db.find().toArray();
    }

    const getPetById = (id: string) => {
        return db.findOne(new ObjectId(id))
    }

    const getPetsByIds = (ids: Array<string>) => {
        const i  = ids.map($ => new ObjectId($));
        return db.find( {_id: {$in: i}} );
    }

    const updatePet = (id: string, pet: PetSchema) => {
        return db.findOneAndReplace({_id: new ObjectId(id)}, pet);
    }

    const createPet = (pet: PetSchema) => {
        return db.insertOne(pet);
    }

    const deletePet = (id: string) => {
        return db.deleteOne({_id: new ObjectId(id)});
    }

    return {getAllPets, getPetById, updatePet, createPet, getPetsByIds, deletePet}
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Как видно из приведенного выше кода, этот сервис представляет собой набор функций, которые получают в параметре код, который будет храниться в базе данных.

Контекст

Контекст или база данных — это файл, в котором мы будем работать. Файл pet-context — это не что иное, как файл, в котором мы хотим подключиться к источнику данных и задать ему тип или схему.

export default function PetContext(fastify: FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance>) {
    if (fastify.mongo.db !== undefined) {
        return fastify.mongo.db.collection<PetSchema>('Pets');
    }
    throw new Error('No DB collection found')
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Просто, не так ли? Потому что это mongo и большая часть сложности находится в схеме, но миграции и другие задачи, связанные с данными, должны быть в этом контексте, то есть в каталоге, где экспортируется только БД и скрыты ее особенности, в данном случае это просто экспорт коллекции.

Схема

Схема — это представление ваших данных, это может быть тип + Объект, это место, где будет находиться база вашего домена, если у вас есть схема базы данных и некоторые другие детали, все это будет находиться внутри этого каталога. Главное, чтобы для тех, кто играет в проекте, были понятны домены и возможность расширения через каталоги и файлы.

без лишних слов схема питомца:

export const Pet = Type.Object({
    name: Type.String(),
    type: Type.Optional(Type.String()),
    ownerID: Type.Optional(Type.String()),
});
export type PetSchema = Static<typeof Pet>;
Войдите в полноэкранный режим Выход из полноэкранного режима

Смотрите, у нас есть Pet, который является схемой базы данных, и его тип, который используется TypeScript. Это та простота, к которой нужно стремиться в проектах узлов, простота, делающая только одно дело, но делающая это одно дело очень хорошо.

Резюме

В целом, мы должны стремиться к простоте и минимализму в наших бэкендах, не пытаться создать больше кода, чем необходимо, всегда стараться оставить энтропию кода близкой к нулю, чтобы обслуживание было возможным.

Я рекомендую читать ссылки, приведенные в начале, поскольку первоисточник, хотя он немного сложнее, является содержанием in natura и часто более эффективен для обучения.

Оцените статью
devanswers.ru
Добавить комментарий