question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Ideas & breaking changes - version 3.0

See original GitHub issue

Marble.js v3 is right around the corner. Before its official release I would like to introduce the incoming new features and potential API breaking changes. This is a place for general discussion about all the changes, their relevance and potential impact to your codebase. Feel free to ask questions and propose improvements. 😊

General overview of new features:

  • official support for TypeScript v3.7
  • official support for RxJS v6.5
  • official support for fp-ts v2.x
  • simplified dependency injection via new useContext hook
  • introducing new module @marblejs/messaging for building Microservices
    • for MVP version with support for AMQP (RabbitMQ) and Redis transport layers
    • I expect more transport layers to be introduced after release
    • I’ll post another issue when needed (or expect a surprise 😎🤪)

Incoming breaking changes::

Context API

fp-ts@2.x brought a major breaking change in it’s API (see changelog). It introduced changes that have a major impact to Context API (eg. Reader monad). What’s new?

More explicit dependency binding. Previous API wasn’t so precise, which could result to confusion, eg. when the dependency is lazily/eagerly evaluated.

Old way:

// eager
bindTo(WsServerToken)(websocketsServer.run),

// lazy
bindTo(WsServerToken)(websocketsServer),

New way:

// eager
bindEagerlyTo(WsServerToken)(websocketsServer),

// lazy
bindTo(WsServerToken)(websocketsServer),
bindLazilyTo(WsServerToken)(websocketsServer),

Reader creation:

Old way:

import { reader } from '@marblejs/core';

const someService = reader.map(ctx => {
  // ...
});

New way:

import { reader } from '@marblejs/core';
import { pipe } from 'fp-ts/lib/pipeable';
import { map } from 'fp-ts/lib/Reader';

const someService = pipe(reader, map(ctx => {
  // ...
}));

The release of fp-ts also had an impact to HTTP and WebSocket server creators. Since the run() method on Reader, etc. has been replaced with a thunk, server creation also applied to this change. Bootstrap thunks are promisified, which means that they will return an instance only when started listening, if not then will throw an error.

Old way:

const server = createServer({
  // ...
});

server.run();

New way:

const server = createServer({
  // ...
});

await server();

Effect interface changes:

Currently Effect interface defines three arguments where the second one is used for accessing contextual client, eg. HttpResponse, WebSocketClient, etc. Typicaly the second argument was not used very often. That’s why in the next major version client parameter will be moved to context object which will result to reduced available number of parameters from 3 to 2:

Old way:

const foo$: WsEffect = (event$, client, meta) =>
  event$.pipe(
    matchEvent('FOO'),
    // meta.ask       ---    context reader
  );

New way:

const foo$: WsEffect = (event$, ctx) =>
  event$.pipe(
    matchEvent('FOO'),
    // ctx.client    ---    contextual client
    // ctx.ask       ---    context reader
  );

This change also implies a much cleaner base Effect interface:

interface Effect<I, O, Client> {
  (input$: Observable<I>, ctx: EffectContext<Client>): Observable<O>;
}

interface EffectContext<T, U extends SchedulerLike = SchedulerLike> {
  ask: ContextProvider;
  scheduler: U;
  client: T;
}

With that change the last argument of Effect interface is no more called as EffectMetadata but rather as EffectContext

When dealing with error or output Effect, the developer had to use the attribute placed in the third effect argument, eg. const effect = (req$, client, { initiator, error }) => ... In the case of ErrorEffect the thrown error is passed to stream directly:

const error$: HttpErrorEffect<HttpError> = req$ =>
  req$.pipe(
    map(({ req, error }) => {
      // ...
    }),
  );

In the case of OutputEffect the message initiator (eg. initial request) is passed to stream directly:

const output$: HttpOutputEffect = out$ =>
  res$.pipe(
    map(({ req, res }) => {
      // ...
    }),
  );

Nightly builds

If you would like to take part in beta testing feel free to try out canary releases. I’ll try to inform here about new builds and changes that they introduce.

CC @tstelzer

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:14
  • Comments:9 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
JozefFlakuscommented, Dec 4, 2019

Another bunch of updates:

Since early version, the framework carries so called “API smell” that I’m not very proud of. The latest nightly build of v3.0 defines the basic Effect interface as follows:

interface Effect<I, O, Client> {
  (input$: Observable<I>, ctx: EffectContext<Client>): Observable<O>;
}

When talking about HTTP server request handling we typically have to deal with two objects:

  1. HttpRequest - http.IncomingMessage
  2. HttpResponse - http.ServerResponse

They nature expects that they need to be carried together, eg. as a tuple. The current HttpEffect carries HttpResponse object inside ctx.client (via second argument). The design makes the API blocked for future improvements and optimization (eg. effects can’t be eagerly instantiated because the function has to be called every time with new HttpResponse instance).

Affected by #181, I would like to present another change that should move the API to the right tracks (PR #185). The plan for upcoming release is to enable further optimizations (#181) and process server requests (messages) as it should be made since the beginning.

interface HttpRequest {
  url: string;
  method: HttpMethod;
  body: Body;
  params: Params;
  query: Query;
  response: HttpResponse;     // 👈 
}
const effect$ = r.pipe(
  r.matchPath('/'),
  r.matchType('GET'),
  r.useEffect((req$, ctx) => req$.pipe(
    map(req => ...),
    // req.response -- HttpResponse object
    // ctx.client -- running HTTP/HTTPS server instance
  )));

Why the interface is not like follows: { req: HttpRequest, res: HttpResponse } Response object is barely used (only in specialized middlewares) thus mapping it each time to req object is very repetitive.

1reaction
JozefFlakuscommented, Feb 5, 2020

The latest v3.0.0-rc.5 introduces two small changes:

  1. deprecation of EffectFactory builder
  2. fp-ts as a required peer dependency (next to rxjs)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Planned breaking changes in version 3.0 | Pact Docs
Planned breaking changes in version 3.0. Rename pacticipant to application; Removal of deprecated relations without a pb; Remove option to ...
Read more >
Breaking Changes · microsoft/TypeScript Wiki - GitHub
These changes list where implementation differs between versions as the spec and compiler are simplified and inconsistencies are corrected. For ...
Read more >
Breaking changes version 3 - Optimizely
This topic describes breaking changes in the Content Delivery API. ... For version 3, some methods that were made obsolete in prior versions...
Read more >
What's New in Apache Kafka 3.0.0 - Confluent
Apache Kafka 3.0 introduces a variety of new features, breaking API changes, and improvements to KRaft—Apache Kafka's built-in consensus ...
Read more >
Announcing TypeScript 3.0 - Microsoft Developer Blogs
Visual Studio 2017 (for version 15.2 or later). ... Despite the new big number, 3.0 has few breaking changes (meaning it should be...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found