Giter VIP home page Giter VIP logo

seneca-vote's People

Contributors

lilsweetcaligula avatar rjrodger avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

seneca-vote's Issues

Use/implement a transaction in CastVote

For example, currently if the plugin crashes when trying to de-normalize a poll rating, the submission of the vote will not be rolled back (i.e. undone). This violates ACID and must be handled via implementation of a transaction.

#44 (comment)

why string should be a code value

The why property should not be a human friendly message - rather an arbitrary code string for use by client code.
Nor should it contain any supporting information - this goes into a details property at the same level.

{ ok: false, why: 'invalid-field', details: { field: { name:'foo', ... }, ... } }

Thus client code can decide what to do with it, generate translated human friendly messages, custom logic, retry, etc, etc

failing test on clean install

$ npm test

> [email protected] test /Users/richard/Projects/seneca/vote
> ./node_modules/jasmine/bin/jasmine.js --config=test/support/jasmine.json

Randomized with seed 04499
Started
.............................F.....................

Failures:
1) the OpenPoll action when the poll with the given title does not exist when bombarded with messages to create a poll with the same title does not result in a race condition
  Message:
    Expected 3 to equal 1.
  Stack:
    Error: Expected 3 to equal 1.
        at <Jasmine>
        at /Users/richard/Projects/seneca/vote/test/lib/open_poll_msg.test.js:176:57
        at runMicrotasks (<anonymous>)
        at runNextTicks (internal/process/task_queues.js:58:5)

51 specs, 1 failure
Finished in 1.666 seconds
Randomized with seed 04499 (jasmine --random=true --seed=04499)
npm ERR! Test failed.  See above for more details.

entity creation

The idiom for entity creation is to use this in situ:

let pollent = seneca.make('sys/poll')

separate utility functions are not needed

Insert into entities specified by the developer the total count of votes

Allow developers to pass an option to the plugin. This option, if passed, will indicate mapping from some entity (indicated by its canon form, e.g. base/name) to the field intended for storing the total count of votes. This field should be updated with the total count of votes after each vote submission.

Example client code:

seneca.use('vote', {
  dependents: {
    'red': { // 'red' is the entity's 'kind' field
      'mars': { // 'mars' is the entity's 'code' field
        totals: {
          'foo/bar': { field: 'votes' },
          'zed/qaz': { field: 'total' }
        }
      }
    }
  }
})

Let's assume we have the following entities in our store (i.e. db):

foo/bar: 
  { id: 101, name:'Q', votes: 0 }

sys/vote:
  { id: 01, kind:'red', code:'mars', voter_id:...}
  { id: 02, kind:'red', code:'mars', voter_id:...}
  { id: 03, kind:'red', code:'jupiter', voter_id:...}
  { id: 04, kind:'blue', code:'earth', voter_id:...}

Now let's assume someone voted with a message that looks like this:

{ sys:vote, poll_id: ..., voter_id:..., kind:'red', code:'mars', dependents: { 'foo/bar': 101 } }

This vote causes a new entity to be created:

{ id: 05, kind:'red', code:'mars', voter_id:...}

With the dependents option specified above in the snippet in mind, after the new vote has been cast, the following votes will contribute toward the total count of votes:

  { id: 01, kind:'red', code:'mars', voter_id:...}
  { id: 02, kind:'red', code:'mars', voter_id:...}
  { id: 05, kind:'red', code:'mars', voter_id:...}

This gives the total number of 3 votes. Notice how we only calculate the total number of votes who match the kind/code fields. Basically, to put it in pseudo-code (SQL in this example):

select count(1) from sys/vote
where poll_id = ? and kind = 'red' and code = 'mars'

However, and this is important - the votes entity in the plugin is built according to the tombstone pattern, which means new votes are created in the store/db as opposed to updating the existing ones. That means, for each vote, only the newest vote by that voter on that poll contributes to the total count.

Now, the dependents option in the snippet above lists entities foo/bar and zed/qaz, and the client who cast the vote, passed a parameter: dependents: { 'foo/bar': 101 }, which means the entity foo/bar with the id of 101 has to be updated to the total count of votes.

If no such dependent exists (e.g. no foo/bar entity exists with the id of 101, requested by the client), then we must respond to the client with an error (respond but do not throw the error!).

So let's assume prior to the vote the following foo/bar record existed in the store/db. This is our "dependent" record":

    { id: 101, name:'Q', votes: 2 } 

After the vote has been cast, the foo/bar record will look like so:

    { id: 101, name:'Q', votes: 3 } 

Votes of vote_type 'up' add +1 to the total count, and votes of vote_type 'down' add -1 to the total count.

P.S.: First step: define some seneca-msg-test scenarios for this - TODO: @rjrodger

Seneca business logic plugin conventions

These are informal, but will become standard. In the return values, use props:

  • ok: boolean in place of status
  • why: string a string code representing the error or other issue if ok === false

Refactor for Version 2 (just notes - not urgent)

This normal mode of use for system plugins is generally:

seneca.use('vote', { ... options ... })

Which adds some set of sys:vote messages, and a plugin called sys_vote.

Some of this is checked by seneca-plugin-validator (as to be added for v2).

The mini-plugins (OpenPoll, GetPoll), etc. will need to be converted to plain messages - this avoids a proliferation of plugins in a client system.

The code base will need to be converted to TypeScript. This will also remove the need for fetchProp, asserts etc as property access can be statically verified.

Fix the vote.updated_at field not getting updated in the CastVote action

If, for example, a user previously upvoted on a poll, and then chose to downvote instead, we update his existing vote to the new status. When that happens, currently, we do not set the vote.updated_at field to the new date, although we should. Please update the vote.updated_at field to the new date whenever the status of a vote is overwritten.

code style notes

  • rename plugin_opts to options, as this is the conventional name used in other core plugins
  • no IIFEs
  • remove fetchProp
  • function names should not start with a capital

Outline race conditions in the README file

During implementation work on seneca-vote, in order to meet certain MVP requirements, such as that there can only ever be a single poll record with the same title, - locks were implemented as part of seneca-vote to prevent race conditions, that may otherwise occur under high loads.

It was ultimately decided to disable the locks by default in order to comply with the rest of the Seneca eco-system. Entity upserts are planned to be added to Seneca entities in the future. They will work as upserts normally do:
https://docs.mongodb.com/drivers/node/fundamentals/crud/write-operations/upsert/
https://www.postgresqltutorial.com/postgresql-upsert/

Further work on the data model of seneca-vote is expected in order to both prevent race conditions and remain compliant with the rest of the Seneca eco-system.

Please outline this information in the README file.

Make the locking feature optional, default to disabled locks

In order to make seneca-vote uniform across the Seneca eco-system, it is best to disable the custom entity locks (./lib/lock.js) by default. Locks can be enabled explicitly by the client by passing a flag via the seneca-vote plugin options.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.