-
Notifications
You must be signed in to change notification settings - Fork 45
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
Rewrite #33
Rewrite #33
Conversation
""" | ||
|
||
|
||
class SubscriptionModel(object): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@victorlin what's your thinking behind suffixing all the models with Model (e.g. SubscriptionModel
) and then also including the type in the method name?
This seems very verbose:
from billy.models import SubscriptionModel
subscription = SubscriptionModel.create_subscription(...)
subscription = SubscriptionModel.get_subscription_by_guid(xxx)
subscription.update_subscription(...)
To me, calling the model Subscription
and the methods get
and update
respectively seems more concise and it doesn't detract from readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mjallday I like to provide expressive naming in most cases, however, yeah, you're right, this is a overkill here. In certain model context, this cannot help readability afterall. I will correct them later.
But for Model suffixing, I think this is fine. The whole model here is different from what it used to be. I try to isolate business logic from SQLAlchemy object and operations. I don't want to expose too much SQLAlchemy details as when one day we like to switch NoSQL or something like that, it would be difficult to get rid of SQLAlchemy everywhere in the project, as I explained in #24. Also, there is table class named Subscription
, I don't want them to be confusing. And from the big picture, it would be a MVC structure, the naming might look like this
[Subscription (table)] <---- [SubscriptionModel (business logic)] <---- [API web controller]
So, I will keep Model suffixing. Until I make this system workable, if you don't like naming style, I can make some change here.
@victorlin going to address one of your comments here: You wrote -
Since you're using a scoped_session and a sessionmaker, you shouldn't have to worry about multi-threading issues.
This is a really inefficient way of doing bulk updates or insertions. If you want to do a bulk-update, using threading is typically a really poor way of doing it. There are constructs in SQLAlchemy that allow you to very easily do bulk updates or insertions. In one call, using one transaction.
There's other ways to do this if you want to pass a SQLAlchemy mapped object between threads. Simply, tell the ORM to stop tracking and instrumenting this object while you're passing it. Use Threads in Python are typically used for I/O heavy applications, so in that case, why not just pass the model, then do a Passing in objects is a very nice way to work with relationships. Not using them really limits the power of SQLAlchemy. |
@@ -14,6 +14,7 @@ def create_tables(): | |||
""" | |||
Creates the tables if they dont exists | |||
""" | |||
print '#'*10, 'Base.metadata', Base.metadata | |||
Base.metadata.create_all(DB_ENGINE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be using alembic for our database migrations.
What I mean is not actually doing updates or insertions with threading. But for more complex business logic. For example, currently, we have I know there is a way I can leverage session to deals with cross thread issue. However, I prefer to keep things simple and stupid (KISS). The Session.refresh() will actually get the record from database by its primary key, just like what you do by passing GUID. So, in this case, for simplicity and consistency, l prefer passing primary keys. However, if passing object is more comfortable for youguys, I will refactory the model to do it the way later (When we reach the stable stage). |
if transaction_type not in self.TYPE_ALL: | ||
raise ValueError('Invalid transaction_type {}'.format(transaction_type)) | ||
transaction = tables.Transaction( | ||
guid='TX' + make_guid(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern should be moved to a base model.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we have duplicated code there. However, I will keep them there for a while. I would like to see what are those common elements shared by all modes, and extract them to a base class or something later. Sometimes by doing OO design too early, it would be over engineering or modeling it wrong. A little deferred refactorying would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use this same pattern. I can share it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@victorlin - definitely agree with you regarding OO design too early making it over engineering or modeling it wrong, but we've been working on this for almost 3 years now. We've got a couple of patterns that we can share w/ you that we've really nailed down over time.
One flaw I see with your method is if you ever have to override any of the uuid creation schemes, it's very hard to do so (you don't expose a method anywhere to do this).
I'll fork your branch of billy and make a pull request so I can demonstrate what I'm talking about. I'll also add you to our internal Balanced project so you can see how advanced it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I am actually building billy with my experience and my practices. I have done things with this kind of structure and patterns for tons of projects. Although, I am doing this for years, I am still improving it gradually.
The reason we have so many discussions here is that we have very different development experience and practices, surely it would take some time to get used to. And yes it would be nice if I can see how you do in the backend systems. I am sure I can learn something from that and apply on billy.
But for now, I can do it with the best productivity in my favor manners (in this week, maybe). So, I will keep working, and in the mean time, I will try get familiar with your style, then gradually refactory and transform into Balanced style. With good testing coverage, we can always switch between different implementation approaches easily. Does it make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely :)
Also, are you going to recurring payout support? This is probably the most important distinguishing feature for Billy. See #15. |
Yes, recurring payout is included in this initial development cycle. |
The features I am going to implement in this cycle are posted in #25 as usage scenario form. If you see anything that is needed badly but not there, please added it in a reply. |
Okay, for now, there are still details to take care, however, we have reached a rough workable version with 100% testing coverage.
To run the test
Create tables database
Start the server
Create a company
result
Create a customer
result
Create a plan
result
Create a subscription
result
Process transactionsWe have a subscription in the database, and it should yield a transaction right away. To process them, here you call
A crontab setting or similar scheduler can be used to process all transactions periodically. What's next?There are still some design decisions to make, such as should we allow modifying subscription, should we process transaction immediately when a subscription is added, I will think about them. I may create issues for them, then we can talk about it and see what kind of design is better. Although the testing coverage is 100%, doesn't mean it is bug-free nor perfect, I am sure there are many edge cases aren't tested. And for testing, we have unit tests and functional tests, they are all on SQLite. I will create a Vagrant environment and writes some integration tests there with PostgreSQL. In the mean time, I may do some refactory, but the priority here are still the tests. With good testing coverage (include edge cases), we can move faster. |
@victorlin you are amazing :) |
@mjallday Well, I think this branch is good to merge back now. It is stable enough and also functional. I will create issues for remaining task later. |
(thread local is used in Balanced-Python)
… without permission
this pull-request is big and old. can we merge it so we don't have to look at 10k line pull requests ever again? |
this looks great +1 |
Restart from a solid ground up
Basic models are built (core business logic and RESTful API are not implemented yet), still not in functional stage. However, we have 97% testing coverage currently. I hope I can keep this through and eventually deliver with 100% testing coverage.
To install and run testing
Only merge this branch back when it is good to go.