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

Feedback requested: AsyncConnection and (soon) AsyncManager #10

Closed
wants to merge 25 commits into from

Conversation

jasononeil
Copy link

@jasononeil jasononeil commented Jul 19, 2017

I would like to (finally) work on getting sys.db.* to work with NodeJS, which of course means making async versions of Connection and Manager (and considering how to handle Object.insert() etc).

Here's what I've done so far:

  • Create an AsyncConnection interface
  • Create class sys.db.MysqlJs implements AsyncConnection {} using this NPM
  • Done some preliminary work in RecordMacros, in preparation for an AsyncManager.
  • Refactored a few existing lines in RecordMacros to use reification because it was easy to do and more readable.

What I want my next steps to be:

  • Create an AsyncManager (or adapt Manager to have async methods)
  • Figure out how to adapt Object to work async.
  • Make an async version of TableCreate.exists() and TableCreate.create().
  • Make an async version of Transaction.run() and Transaction.isDeadlock().
  • Write an async version of the unit tests, make sure they run in Travis
  • Clean up the PR, remove the NPM stuff etc. Consider moving MysqlJs class to a separate repo.

But before I go further, I have some decisions to make, and would appreciate feedback:

1. Do we want this in record-macros or as a separate haxelib?

If we want it in a separate haxelib, ignore all further questions and I'll figure it out myself :) But I'd like to merge it in.

2. What approach should I take with the manager?

The existing Manager class has a lot of very old code and covers a lot of edge cases, so I either want to re-use it, or do a clean implementation. My options are:

  1. Adapt Manager to expose both sync and async methods. Actually shouldn't be that hard... these are the only two places we need to update (and then the flow-ons to other functions):

    • cnx.lastInsertId() in doInsert()
    • cnx.request(sql) in unsafeExecute()
  2. Create a new AsyncManager class, but start from a copy/paste of the old Manager class. Advantage: keep the two separate and a bit cleaner. Disadvantage: code repetition, having to fix bugs in two places etc.

  3. Create a new AsyncManager class from scratch, but make it less magical, so no automatic caching etc. I feel there's two many edge cases here for me to cover appropriately in a first build.

3. Object.update, Object.insert, etc.

Should I bother trying to adapt these functions to be async? One idea would be to define the signature like so:

public function insert(?cb:CompletionCallback):Void
public function delete(?cb:CompletionCallback):Void
public function update(?cb:CompletionCallback):Void
public function lock(?cb:CompletionCallback):Void

Where a completion callback is optional, and triggered on both sync and async platforms, but can safely be ignored on sync platforms.


Thoughts? Comments? I'm working on this as a side project for the next week and a bit so would love your thoughts

- AsyncConnection uses standard NodeJS callbacks, rather than returning promises or futures or whatever.  The reason is: it keeps the interface generic for libraries like PromHx or Tink_Core.  It avoids using platform specific things like js.Promise.

- I've got an example MysqlJs lib.  This probably should belong in a separate lib but during this early stage it is easier to test here.  This uses the NPM 'mysql', and has the appropriate '@:jsRequire' metadata.

- Next step: see if we can create an AsyncManager...
These will be used in the upcoming AsyncManager.hx

Added: macroGetAsync, macroSearchAsync, macroCountAsync, macroDeleteAsync

Please note, I also updated the sync versions of the methods to use macro reification
as I find it easier to read.
...so we can use a sync Connection where an AsyncConnection is expected
@bablukid
Copy link
Contributor

I'm not a nodejs user but I think it's important to keep this in one single library.

About question 2 : why not having 3 classes, one for sync IO, one for async IO, one for the common logic ( SQL generation...etc )

Currently the Manager class is really fat ( 740 lines ) so it would become more maintainable if concerns are separated. Its typically a case of "god object" antipattern :)

We will share logic between the Manager and AsyncManager in BaseManager.

This is a first step in refactoring.
The unit tests are passing (yay!)
…tion.

Because these are implemented by the AsyncConnectionWrapper, we can move
forward with deprecating the use of `Manager.quoteList()` and
`Manager.quoteAny()` without changing the interface definition of
`sys.db.Connection`, which would be a breaking change for the standard lib.

Also in this commit:

- implement missing function body for `AsyncConnectionWrapper.lastInsertId()`
- minor change to the placeholder async tests
@jasononeil
Copy link
Author

Great feedback @bablukid

I've started refactoring by creating a separate BaseManager class which has the core logic, and moving the public interface out to a class Manager extends BaseManager {}, and eventually a class AsyncManager extends BaseManager.

As I begin to unwind this there is a fair bit of logic that is difficult to re-use in an async context, so lots of refactoring may be required, which makes the PR harder to code review. Hopefully it will make the library more maintainable though!

@jasononeil
Copy link
Author

I've also realised that sys.db.Connection comes from the standard library, so I will need to eventually open a pull request on HaxeFoundation/haxe/. I'll keep the work in progress here until that is closer to being ready though.

…class.

This allows the synchronous Manager to maintain the existing global
static object cache, but an async manager to have a per-instance
local cache.
I assume they used to do something productive, but do not anymore.

This is not exposed as public API, however it is possible that a subclass
exists in someone's codebase that does something in these methods - but
it's not documented in any way, so I think getting rid of it is smart.

If anyone objects to this change, then perhaps we can revert this commit
and document the purpose of the functions so that it is known.
Where possible separate SQL generation methods from methods
that actually return the objects.
…thods to Manager

- Manager is basically a version of BaseManager that enforces the Connection is always synchronous
- As such, it's safe to call the "async" version of the method, and assume a result will be ready immediately
- Please note that Manager.getCnx() now returns AsyncConnection not Connection, so I had to change TableCreate to get the connection via Manager.cnx
	- I think TableCreate will need to be refactored into sync / async versions anyway
Mostly to use `BaseManager.someMethod()` or `manager.getCnx().someMethod()`
instead of `Manager.someMethod()`
And clean up some of the resulting errors that were uncovered.
Neko tests are now passing again.
I'm not very happy with the handling of optional arguments in macros,
but it will do for now.  Any suggestions for a better approach are welcome!
Please note, technically this is a breaking change: for example
ufront.db.Object extends sys.db.Object and overrides insert()
and update(), whose signatures have now changed.

However for the normal use cases, where you extend the class but
do not override those functions, the existing usage will still
be fully compatible.  For example - all the unit tests still pass.
@jasononeil
Copy link
Author

Well, I've done it:

  • New AsyncManager class that works with Node JS.
  • Manager is refactored into separate BaseManager, Manager (sync) and the new AsyncManager class
  • All macros adjusted to suit slight changes to API
  • Optional async signatures added to sys.db.Object
  • All existing neko (sync) tests passing.

I plan to write async versions of the unit tests and run against a real code base. But other than that it would be good to get some more feedback, and see if people are happy to merge :)

basically PHP is complaining that BaseManager.nullCompare() was
different to Manager.nullCompare().  They were, but in Haxe that
doesn't matter for static functions.  Apparently it does in PHP?

Given that it's private in BaseManager I just changed the name.
The difference is that the Node library expects a field called "password"
but other Haxe connections expect a field called "pass"
@PXshadow
Copy link

PXshadow commented Mar 5, 2018

Currently It's not possible to test on latest haxe versions because #12 has not been merged.

@bablukid
Copy link
Contributor

Thanks for the big work Jason !

Do you think it would be possible to easily migrate synchronous code for neko to nodejs, just by adding @await ?

Like var res = @await db.User.manager.get(1,false);

BTW I heard await will be implemented in haxe 4, do you know more about it ?

@jasononeil
Copy link
Author

Closing old/stale PRs

@jasononeil jasononeil closed this Aug 17, 2019
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.

3 participants