One approach is to come up with some way of tracking your changes, then dumping them to the database in bulk. There are different approaches to this, but you will need to have something keep track of all the original values, then figure out what has changed, then batch that up into inserts, updates, and deletes.
Martin Fowler calls the object that keeps track of this a Unit of Work. This object handles CRUD for all the business objects. From the link, it should be clear that you use registerNew, registerDirty, registerClean, registerDeleted from within properties setters, and use rollback or commit when you are ready to save everything.
There is discussion of pros and cons of various implementation details in Patterns of Enterprise Application Architecture as well. Some things to think about are how often to hit the database, what to do if clients are engaged in a business transaction longer than your database transaction can feasibly be, and whether to talk to your Unit of Work via events or directly.
This functionality will be part of what an ORM does best, and if you can find one it will be better than writing one. But understanding the concepts would still be valuable.
Primary reason to use a class is that there is a concept in domain you are trying to express in your code and you want to codify this concept in some way. Usually, classes are created when you, as a developer, see, through your years of experience, that there is a concept in what you are trying to achieve and you want to explicitly convert this concept into code.
But also, many of the concept show up after you have written the code, quite often as various code smells. There are two examples :
Multiple values being passed around together.
Be it Point or Customer, you can sometimes see that there are multiple (3+) variable all being passes around together. Code usually needs all, or most of those variables and even if it doesn't directly need them, it's dependencies might do. This is great opportunity to introduce a class to encapsulate all those variables. After that, you realize that many of the functions that work on those fields could actually be part of the class itself. And then, you might realize this is a concept within your domain, that you missed when doing analysis or design.
Big functions
It happened multiple times to me, that I found a function that was just too long. But when I tried to divide it into smaller functions, it resulted in all of the function's state being passed around in parameters. This is great opportunity to introduce a class to represent the whole function itself. The function's parameters might become a classe's constructor. The function's variables become fields. And then, you can easily separate the big function into smaller ones, because they all use shared state instead of relying on parameter passing. And then, you realize some of the if
s and switch
es might be represented as subclasses instead of code flow constructs, creating rich, maintainable and expressive representation instead of one huge hard-to follow and maintain function. This should make you think : if this piece of code was so complex, shouldn't it be some kind of concept in domain I'm working in?
Classes as "structures with functions"
You are saying you have experience with procedural programming. Then you must know about structures and their use cases. Structures and classes overlap in many of those use cases. Some people even say that classes are just structures with functions bolted onto them and that they are no different than procedural programming. While I disagree, it might be good way for you to start thinking about classes. So whenever you would create a structure, you instead create a non-static class. The rest then follows.
Transparent dependencies and control flow
Another problem with static classes is that of dependency. If method of a class is static, then it becomes hard to tell what code actually uses this method. It becomes even worse problem if static state (in form of static fields) is involved. It becomes extremely hard to follow the flow of a code when you don't know what other methods might be involved. If you use non-static classes, it becomes much cleaner, because you know what piece of memory method can modify, instead of working with global state. Actually, global state is the keyword here. It is generally known problems with global state range from pain in the ass to total nightmare. You should be striving to minimize amount of global state in your programs. Usage of non-static classes should be prioritized over static classes whenever possible.
Best Answer
This is a more well-formed transcription of my initial comment under your question. The answers to questions addressed by the OP may be found at the bottom of this answer. Also please check the important note located at the same place.
What you are currently describing, Sipo, is a design pattern called Active record. As with everything, even this one has found its place among programmers, but has been discarded in favour of repository and the data mapper patterns for one simple reason, scalability.
In short, an active record is an object, which:
You address several issues with your current design and the main problem of your design is addressed in the last, 6th, point (last but not least, I guess). When you have a class for which you are designing a constructor and you do not even know what the constructor should do, the class is probably doing something wrong. That happened in your case.
But fixing the design is actually pretty simple by splitting the entity representation and CRUD logic into two (or more) classes.
This is what your design looks like now:
Employee
- contains information about the employee structure (its attributes) and methods how to modify the entity (if you decide to go the mutable way), contains CRUD logic for theEmployee
entity, can return a list ofEmployee
objects, accepts anEmployee
object when you want to update an employee, can return a singleEmployee
through a method likegetSingleById(id : string) : Employee
Wow, the class seems huge.
This will be the proposed solution:
Employee
- contains information about the employee structure (its attributes) and methods how to modify the entity (if you decide to go the mutable way)EmployeeRepository
- contains CRUD logic for theEmployee
entity, can return a list ofEmployee
objects, accepts anEmployee
object when you want to update an employee, can return a singleEmployee
through a method likegetSingleById(id : string) : Employee
Have you heard of separation of concerns? No, you will now. It is the less strict version of the Single Responsibility Principle, which says a class should actually have only one responsibility, or as Uncle Bob says:
It is quite clear that if I was able to clearly split your initial class into two which still have a well rounded interface, the initial class was probably doing too much, and it was.
What is great about the repository pattern, it not only acts as an abstraction to provide a middle layer between database (which can be anything, file, noSQL, SQL, object-oriented one), but it does not even need to be a concrete class. In many OO languages, you can define the interface as an actual
interface
(or a class with a pure virtual method if you are in C++) and then have multiple implementations.This completely lifts the decision whether a repository is an actual implementation of you are simply relying on the interface by actually relying on a structure with the
interface
keyword. And repository is exactly that, it is an fancy term for data layer abstraction, namely mapping data to your domain and vice versa.Another great thing about separating it into (at least) two classes is that now the
Employee
class can clearly manage its own data and do it very well, because it does not need to take care of other difficult things.Question 6: So what should the constructor do in the newly created
Employee
class? It is simple. It should take the arguments, check if they are valid (such as an age shouldn't probably be negative or name shouldn't be empty), raise an error when the data was invalid and if the validation passed assign the arguments to private variables of the entity. It now cannot communicate with the database, because it simply has no idea how to do it.Question 4: Cannot be answered at all, not generally, because the answer heavily depends on what exactly you need.
Question 5: Now that you have separated the bloated class into two, you can have multiple update methods directly on the
Employee
class, likechangeUsername
,markAsDeceased
, which will manipulate the data of theEmployee
class only in RAM and then you could introduce a method such asregisterDirty
from the Unit of Work pattern to the repository class, through which you would let the repository know that this object has changed properties and will need to be updated after you call thecommit
method.Obviously, for an update an object requires to have an id and thus be already saved, and it's the repository's responbitility to detect this and raise an error when the criteria is not met.
Question 3: If you decide to go with the Unit of Work pattern, the
create
method will now beregisterNew
. If you do not, I would probably call itsave
instead. The goal of a repository is to provide an abstraction between the domain and the data layer, because of this I would recommend you that this method (be itregisterNew
orsave
) accepts theEmployee
object and it is up to the classes implementing the repository interface, which attributes they decide to take out of the entity. Passing an entire object is better so you do not need to have many optional parameters.Question 2: Both methods will now be a part of the repository interface and they do not violate the single responsibility principle. The responsibility of the repository is to provide CRUD operations for the
Employee
objects, that is what it does (besides Read and Delete, CRUD translates to both Create and Update). Obviously, you could split the repository even further by having anEmployeeUpdateRepository
and so forth, but that is rarely needed and a single implementation can usually contain all CRUD operations.Question 1: You ended up with a simple
Employee
class which will now (among other attributes) have id. Whether the id is filled or empty (ornull
) depends on whether the object has been already saved. Nonetheless, an id is still an attribute the entity owns and the responsibility of theEmployee
entity is to take care of its attributes, hence taking care of its id.Whether an entity does or does not have an id does not usually matter untill you try to do some persistence-logic on it. As mentioned in the answer to the question 5, it is the repository's responsibility to detect you aren't trying to save an entity which has already been saved or trying to update an entity without an id.
Important note
Please be aware that although separation of concerns is great, actually designing a functional repository layer is quite a tedious work and in my experience is a bit more difficult to get right than the active record approach. But you will end up with a design which is far more flexible and scalable, which may be a good thing.