ryan-mars / stochastic

TypeScript framework for building event-driven services. Easily go from Event Storming → Code.
MIT License
6 stars 1 forks source link

Enterprise Developer Experience #106

Open davehimself opened 3 years ago

davehimself commented 3 years ago

In an effort to meet the target audience (i.e., enterprise developers new to distributed applications and/or cloud) where they are, adopt development paradigms similar to what they use today. Ideally, the paradigms are language features (or similar).

For example, the following is a snippet from one of the Stochastic examples:

export const AddMiles = new Command(
  {
    __filename,
    intent: AddMilesIntent,
    store: WorldPassAccountStore,
    events: [WorldPassMilesAwarded],
    confirmation: undefined,
  },
  context => async (command, store) => {
    const { state } = await store.get(command.worldPassAccountNo)
    // .. check state before ...
    return [
      new WorldPassMilesAwarded({ worldPassAccountNo: command.worldPassAccountNo, milesAdded: command.milesToAdd }),
    ]
  },
)

Using TypeScript decorators, the following captures the sentiment of this issue, using idioms common to enterprise development:

@Command({
  __filename,
  intent: AddMilesIntent,
  store: WorldPassAccountStore,
  events: [WorldPassMilesAwarded],
  confirmation: undefined,
})
export async function AddMiles(command, store): WorldPassMilesAwarded[] {
  const { state } = await store.get(command.worldPassAccountNo)
  // .. check state before ...
  return [
    new WorldPassMilesAwarded({ worldPassAccountNo: command.worldPassAccountNo, milesAdded: command.milesToAdd }),
  ]
}
ryan-mars commented 3 years ago

Generally I'm off-put by the look of decorators and how they're often used to hide behavior. I wonder if I'm in the wrong given their persistent use in popular frameworks such as Angular and Spring. Other serverless frameworks are also using them and I've not seen any pushback on the pattern. I suppose this means developers are at least ok with them.

ECMA Script decorators have been stuck at Stage 2 since 2019-01-15. In Typescript they are hidden behind the compiler flag --experimentalDecorators.

Originally we wanted Stochastic to feel like a functional architecture while allowing developers the flexibility to use whatever feels most idiomatic to them within the handler functions.

Ultimately I want whatever will make it easiest for people to grok their code. Code should be written first and foremost for humans to understand and only incidentally for compilers.

@sam-goodwin How do you feel about this? How would this impact type narrowing on handler functions?

sam-goodwin commented 3 years ago

I have also worried about how enterprise developers may be expecting annotations, but I think the idea that annotations = enterprise software is becoming less true over time, just look at React and the CDK.

There are two reasons I think we should not use decorators.

First, decorators do not capture information at compile time (at the type level). See https://github.com/microsoft/TypeScript/issues/4881. We make use of that type-level information to provide helpful inferences that simplify and constraint the DX:

image

We can validate that a type-signature is correct with decorators but we can not infer anything from them.

If you look at our BoundedContext object and the associated downstream constructs, we make use of type level information to derive type safe interfaces for a specific instance of a BoundedContext. At the type level, we can distinguish between a Command and a Policy and can therefore provide useful type hints in the IDE that guide the user when instantiating Constructs or a Runtime. @ryan-mars is using that information to provide a lean, context-aware local runtime testing library for stochastic. These features all leverage TypeScript's mapped types which is something Java and other "enterprisey" languages don't have.

Another reason to not use decorators is that this doesn't make much sense.

@Command()
@Policy()
function foo()

The fact that something is a Command or a Policy is not metadata and should not be treated as such. Is expecting a developer to instantiate a new Command or a new Policy too far of a divergence from current patterns? Is it too difficult to understand? To me, they look like react functional components. I don't think we are looking to exactly repeat past patterns. We want to learn from them and then make use of more advanced language features to (hopefully) provide a better experience. The CDK also diverges from past patterns with its (scope, id, props) tuple constructor pattern and React has largely abandoned classes in favor of functions and hooks. Functional programming is bleeding into mainstream and I think stochastic should align with that instead OOP.

If we were to decide to move to decorators then we would be abandoning much of our current DX. We can leverage them where it makes sense, but I don't agree we should use them for our core declarations, Command, Policy, etc. I do agree with your sentiment that enterprise developers may be expecting something like Spring/Axon annotations and acknowledge it as a DX risk we need to collect feedback on. But using decorators would limit us in ways that I think would be decremental to the overall DX. I hope our DX is simple enough such that this is not a problem.

sam-goodwin commented 3 years ago

To emphasize the points above, we should provide a demo of how we make use of these declarations to guide the experience. The contents of the new Command directly guide the user on what the function signature should be. This DX is a part of our strategy to keep the developer in the IDE instead of the docs. The IDE should teach and protect the developer. Decorators are less helpful in this regard.

sam-goodwin commented 3 years ago

If we were to switch to a decorator approach, then I think we would end up looking a lot like TypeGraphQL: https://github.com/MichalLytek/type-graphql. It's definitely something we should consider.

davehimself commented 3 years ago

To emphasize the points above, we should provide a demo of how we make use of these declarations to guide the experience. The contents of the new Command directly guide the user on what the function signature should be. This DX is a part of our strategy to keep the developer in the IDE instead of the docs. The IDE should teach and protect the developer. Decorators are less helpful in this regard.

I have to disagree here. If the DX strategy is based on keeping the developer in the IDE, that's fine. VB6 did that, as well, and had a lot of success. As did the Borland products before VB6 (enter irony). However, the generated code was untenable. You needed the proprietary IDE in order to modify the code. IDE's should not teach and protect the developer. That's, arguably, the job of the compiler. IDE's should be a force multiplier.

So, is the direction of the DX to create a vscode extension? Or a new compiler? Something else?

sam-goodwin commented 3 years ago

So, is the direction of the DX to create a vscode extension? Or a new compiler? Something else?

No, it is done with pure TypeScript. The types flow throughout the DX. Decorators don't have this capability.