smartrent / grizzly

Elixir Z-Wave Library

Home Page:https://hex.pm/packages/grizzly

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Refactor and Migration

mattludwigs opened this issue · comments

I have been thinking about refactoring Grizzly for a very long time now. However, migrating and ensuring that we don't break something in a catastrophic way is a challenge.

Some high-level ideas of the refactor:

Move away from Grizzly.send_command/4 as the main API call

I think we still expose it but it will ultimately change a bit. I think a better approach would be to provide "higher-level" modules that wrap Grizzly.send_command where the API is the commands the command class can send. To start we can just leave most things as is by just wrapping what is currently there. However, by exposing a new API we can slowly migrate implementation details without breaking everything at once.

However, these higher-level APIs would just call into a Grizzly.send_command still, so the current philosophy of Grizzly (minimize sid-effect causing functions) would still be maintained.

Remove Grizzly.ZWave.Commands.* namespace

Basically, the end game here to delete all the modules for each command. Rather I think we should move them into the command class level module under Grizzly.ZWave.CommandClass.* and have them just be functions that output the right command structure. This will delete tons of code and bring similar things into one module. Hopefully, with those two things, we can get better maintaining and readability within the code and an overall smaller codebase.

Refactor Grizzly.ZWave.Command.t()

I think that structure should be

%{
  name: atom(),
  encode: (command.t() -> iodata),
  response: (binary() -> command.t()) | nil,
  params: keyword()
}

This will allow us to delete Grizzly.ZWave.Command module and behavior (as we are moving away from all the command modules). Moreover, the :response field will be either a function that outputs some of the command response expected by sending the command or nil. First, I use to think that what ought to be a response to a particular commadn should be separate from the command itself, however, the specification actually ties these together so I think it is okay that we do too. Also, when the value is nil the command does not expect a response.

Moreover, I think Grizzly.send_command/4 will turn into Grizzly.send_command(node_id(), command.t()) so that others can develop their own commands and send them without introducing behaviors or protocols.

Update docs to group modules

The docs currently are really overwhelming. I think that once we start moving things out we can group the Z-Wave specification stuff and Grizzly stuff separately - which should help users dig into the documentation. Right now answering the question "How do I turn on a light?" isn't really answerable in the docs outside the cookbook or without some background knowledge in Z-Wave.

Clean up some code along the way

This refactor will force some adjustments to the runtime side of Grizzly that I think are much needed but currently are hard to touch. For example, the command lifecycle will need to be updated to use the command.t() structure (which I think will be simpler, but that is hard to prove out right now). Also, inclusions and exclusions can be cleaned up a bit - even right now I think there are hard-to-find bugs hanging out in that code - not to mention the API is a bit awkward.

Why not

First, there is a solid amount of code relying on the current version of Grizzly. However, I think a slow migration is possible.

With the slow migration, we will be developing new code along with support and maybe even develop on some older code at the same time. There will be a bit of bloat due to this, but I think that can be minimized and the end result will be leaner, easier to maintain, and a more user-friendly Grizzly.

Also, when doing a slow migration there will be two ways of sending commands so documentation and user codebases might look a bit funny for a bit.

Summary

I think moving in this direction would benefit the code base and users of Grizzly. I think it can be iterative and a slow migration that can pose little risk. We can deprecate the old way as the new way is supported and allow time for updates. Part of me thinks this might put us on the path to a v1.0.0 release because the API will start to feel solidified, but that is just me daydreaming right now.

For an example of the first baby steps towards this see #477. Basically, this just wraps the current Grizzly.send_command.