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

Railway Oriented Programming in interpreted languages like Ruby #4

Open
blatter2016 opened this issue Apr 22, 2024 · 0 comments
Open

Comments

@blatter2016
Copy link

Hi Scott,

This is more of a bunch of questions than an issue.

For some background, my team and I are big fans of your work and we’re kind of in the middle of adopting Railway Oriented Programming in our little corner of the world. Specifically, in our codebase, we’ve taken inspiration from the Result datatype and we have crafted most of our functions/methods to return it, so that they can be plumbed together as clear, readable pipelines.

An example would be something like:

Result.new(input_params)
  .and_then(:&validateOrder)
  .and_then(:&commitOrder)
  # … and so on

However, we are primarily a Ruby shop, and porting ROP to work in Rubyland has led to quite a few discussions, especially regarding what must be passed along in these pipelines. To use the snippet shared above, what should the inputs of validateOrder and commitOrder`?

Since Ruby lacks any compile time checking, we’ve tried to work around this by enforcing some styling standards:

  • Functions that act as pipeline stages must accept hash objects as inputs which can then be destructured within the function for extracting relevant information
  • Pipeline stages that synthesize new data for use in later pipeline stages must populate this input hash object with the synthesized data so that it can be extracted in later stages where required.

To ground it with example using the shared snippet above, the following would be the definitions of validateOrder and commitOrder functions:

def validateOrder(hash):
  user_id = hash[“user_id”]
  product_id = hash[“product_id”]
  .
  hash[“validated_order”] = ValidatedOrder(user_id, product_id) 
  

def commitOrder(hash):
  validated_order = hash[“validated_order”]
  

While this seems to work for us for the time being, there are concerns about how such a methodology scales as the business logic grows more complex, when more pipeline stages must be added and more data requires passing from one stage to another.

While the high-level pipeline looks very elegant and concisely describes the business logic, a concern is how this intermediate hash object grows in complexity/size.

So I guess my questions are the following:

  • What are your thoughts on this way of message passing between the different stages of a pipeline? TBH I don’t think this is a problem strictly limited to interpreted languages as more complex pipeline setups in any language using ROP probably involve creating/passing values for stages much later in the pipelines. For example, in F#, what if you’re confronted with the following case:
Request
  |> createUser
  |> generateVerificationEmail
  |> doSomethingWithUser  // how does user from createUser find its way here
  • I understand that your codebase for ROP is mostly in F# (which is fantastic btw), but what are your thoughts/opinions on the portability to other languages (say Ruby for ex). There are tons of libraries out there for Ruby that claim inspiration from ROP but seem to do so through DSLs that differ significantly/create a learning curve which I’m not sure is worth it.

Apologies for the long essay, but it would be great to hear your thoughts. Do let me know if I’ve been unclear anywhere or if I can elaborate on any detail.

Regards

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

No branches or pull requests

1 participant