Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full Review PR #1

Closed
wants to merge 73 commits into from
Closed

Full Review PR #1

wants to merge 73 commits into from

Conversation

dcadenas
Copy link
Contributor

This is just to easy the review feedback process for the whole project which is already living in main

@boreq boreq self-assigned this Dec 19, 2023
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20.10.0'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the docs I think package caching may not be enabled by default but seeing that runs are relatively fast right now it probably doesn't matter. May be something to look at in the future though.

@@ -0,0 +1,20 @@
FROM node:16-alpine

RUN npm install -g pnpm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, I've never used pnpm before, only yarn.

src/routes.js Outdated
);

router.post(
"/.well-known/nostr.json",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this for the first time I find it a bit weird that we post to a .well-known file and I think a normal REST approach of creating routes would be fine (e.g. /names) as well. That being said I really don't think this matters that much.

src/routes.js Outdated
Comment on lines 39 to 70
validateSchema(postNip05),
extractNip05Name,
nip98Auth(validatePubkey),
asyncHandler("postNip05", async (req, res) => {
const {
data: { pubkey, relays },
} = req.body;

const name = req.nip05Name;
const currentPubkey = await req.redis.get(`pubkey:${name}`);

if (currentPubkey && currentPubkey !== pubkey) {
return res
.status(409)
.send(
"Conflict: pubkey already exists, you can only change associated relays."
);
}

const pipeline = req.redis.multi();
pipeline.set(`pubkey:${name}`, pubkey);
pipeline.del(`relays:${pubkey}`);
if (relays?.length) {
pipeline.sadd(`relays:${pubkey}`, ...relays);
}

const result = await pipeline.exec();
logger.info(`Added ${name} with pubkey ${pubkey}`);

res.status(200).json();
})
);
Copy link

@boreq boreq Dec 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am worried that we ended up with domain logic in the HTTP handlers. What I can see here is that in this HTTP handler we execute domain logic which seems to be validating data, verifying signatures, and then we even end up touching the database.

Whenever I encountered such approaches in the past they quickly proved to be hard to maintain and fragile whenever the project reached any level of complexity. From my perspective anything related to either domain logic or databases shouldn't show up in the HTTP handlers.

What if we want to execute the same code under different circumstances e.g. from CLI? It would be easy to miss some of those checks. What if we want to replace the database layer?

Looking at this I see that the code doesn't seem to be divided into layers with specific responsibility e.g.:

  • domain layer which knowns nothing about what database is used
  • database layer which doesn't contain any domain logic whatsoever so that it can be easily maintained and replaced
  • application layer which glues the domain and database/adapters together
  • http/cli handlers and other ports which just call things from the application layer

This is just an example ofc and maybe my brain is too hardwired to follow things like hex architecture but at least some clear domain code which is database agnostic and can be called from various places is good to see. There are many different approaches to how to layer the code in web apps like this. Again, maybe it doesn't matter but this code lights up red lights in my head.

Copy link

@boreq boreq Dec 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also all of this follows the imperative validateData(data) structure of calls instead of much more safe approach of simply making invalid state impossible to exist in memory by using constructors.

For example instead of doing something like:

let event = extractEvent(request);
if !eventValid(event) {
    // error
}

saveEvent(event);

A much more safe approach which makes omitting validation impossible is to create a strong type which then gets passed along with some validations in the constructor. I don't understand how to do this in JS but surely it is possible. I think classes with constructors are a thing now?

let validEvent = new ValidEvent(request);
saveEvent(validEvent);


func constructorOfValidEventButUnfortunatelyFilipDoesNotKnowJavaScipt(request) {
// unpack request
// validate data
// return strong type "ValidEvent"
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that all of this boils down to the term "Domain Driven Design" basically.

@dcadenas dcadenas closed this May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants