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

Split programming model out of worker (library side) #1

Merged
merged 10 commits into from Aug 18, 2022

Conversation

ejizba
Copy link
Contributor

@ejizba ejizba commented Jul 21, 2022

Related to Azure/azure-functions-nodejs-worker#568

Here's the PR for the worker side: Azure/azure-functions-nodejs-worker#608

The most important file to look at is src/InvocationModel.ts

Copy link
Contributor

@hossam-nasr hossam-nasr left a comment

Choose a reason for hiding this comment

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

In general, lgtm :) I left a few nits/questions, but nothing I wanna block over. Also assuming that most of the code is copied over from the old worker, while modifying types to use types provided by the core API, so didn't spend too much analyzing the details of what the invocation model does

package.json Show resolved Hide resolved
package.json Show resolved Hide resolved
scripts/updateVersion.ts Outdated Show resolved Hide resolved
Comment on lines +4 to +12
import { AzureFunction, Context } from '@azure/functions';
import * as coreTypes from '@azure/functions-core';
import {
CoreInvocationContext,
InvocationArguments,
RpcInvocationResponse,
RpcLog,
RpcParameterBinding,
} from '@azure/functions-core';
Copy link
Contributor

Choose a reason for hiding this comment

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

These imports are weird to me.

When importing AzureFunction and Context from @azure/functions, are we importing them from the current npm package (i.e. the worker), or are we importing them from ourselves?

Also, why are we importing * as coreTypes from @azure/functions-core, while excluding CoreInvocationContext, InvocationArguments, etc., and importing them on their own?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When importing AzureFunction and Context from @azure/functions, are we importing them from the current npm package (i.e. the worker), or are we importing them from ourselves?

I'm a bit confused by this question. The "current npm package" is not the worker, it's the library. And it really just translates to the types/index.d.ts file in this repo (relevant line in tsconfig here). We import two-ish things from "ourselves" - for things that go away after you compile to JavaScript (think interface and type) we will import from the types file in this repo (aka @azure/functions). For other things that are real source code and actually stay when compiled to javascript (class, const, function etc.) we will import from the src folder directly

Also, why are we importing * as coreTypes from @azure/functions-core, while excluding CoreInvocationContext, InvocationArguments, etc., and importing them on their own?

This is easier to answer. I prefer "importing them on their own". However, I use * as coreTypes for lines like this where the class name and interface name are the same and would conflict with each other:

export class InvocationModel implements coreTypes.InvocationModel {

const info = this.#funcInfo;

// Allow HTTP response from context.res if HTTP response is not defined from the context.bindings object
if (info.httpOutputName && context.res && context.bindings[info.httpOutputName] === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming this is just copying old code, but just a javascript question here: when you type this condition like this, does this check that each of info.httpOutputName, context,res, etc., are each === undefined, or does this first evaluate info.httpOutputName as a truth value (i.e., info.httpOutputName !== undefined), then does the same for context.res, etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah just copying code.

First of all, info.httpOutputName doesn't just check === undefined. The condition will be false for any false-y value like undefined, null, '', 0, false and maybe more. Sometimes this can lead to unexpected behavior and weird bugs, but we have lint rules in place to help with that.

Secondly, if the first condition returns false, it will not check the following conditions for an && condition. So if info.httpOutputName is false, it will not check context.res.

I think that's what you were asking...

Comment on lines +4 to +5
import * as coreTypes from '@azure/functions-core';
import { CoreInvocationContext, setProgrammingModel } from '@azure/functions-core';
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here about imports

import { InvocationModel } from './InvocationModel';

class ProgrammingModel implements coreTypes.ProgrammingModel {
name = '@azure/functions';
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the name of the programming model the same as the name of the npm package?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, although it doesn't really matter. I'm just using the name here for tracking purposes in telemetry

userLogCallback: UserLogCallback
request: RpcInvocationRequest,
userLogCallback: UserLogCallback,
doneEmitter: EventEmitter
Copy link
Contributor

Choose a reason for hiding this comment

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

For the most part, this function seem to be copy-pasted from the old worker. The only difference I see here is that, before, doneEmitter was created by this function, whereas now it's passed to it. Curious what difference it makes and why this decision was made?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed to share doneEmitter between the InvocationModel methods getArguments, invokeFunction, and getResponse and it was easiest to create it on the class and pass it in here. Suffice to say it was just a byproduct of how the new code is structured and not some sort of grand design decision


import Module = require('module');

export function setupTestCoreApi(): void {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if I'm following this correctly, but it seems like here we're defining our own mock core API for testing purposes, which isn't necessarily the same core API provided by the worker. This means that if, for whatever reason, this API changes from the worker, we have to update it here too. I don't have ideas off the top of my head, but is there any way we can test against the actual core API directly, or link the two somehow?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

First of all, we don't have to mock the entire core api, just a few trivial enums. So I'm not worried about the engineering upkeep of the api changing

And yes there is a way to link the two - you'd have to essentially run the func cli as a part of the test. However that's more of an "end-to-end" test and even if we do that (which I want to do, just not sure when) it would not replace these unit tests. End-to-end tests take a lot longer to run and usually integrate external components. Unit tests should be super fast/simple and not rely on external components

@ejizba ejizba merged commit 0911b73 into v3.x Aug 18, 2022
@ejizba ejizba deleted the ej/split-framework branch August 18, 2022 16:42
@ejizba ejizba mentioned this pull request Aug 23, 2022
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.

None yet

2 participants