7

We have a SOA-based system. The service methods are like:

  1. UpdateEntity(Entity entity)

For small entities, it's all fine. However, when entities get bigger and bigger, to update one property we should follow this pattern in UI:

  1. Get parameters from UI (user)
  2. Create an instance of the Entity, using those parameters
  3. Get the entity from service
  4. Write code to fill the unchanged properties
  5. Give the result entity to the service

Another option that I've experienced in previous experiences is to create semantic update methods for each update scenario. In other words instead of having one global all-encompasing update method, we had many ad-hoc parametric methods. For example, for the User entity, instead of having UpdateUser (User user) method, we had these methods:

  1. ChangeUserPassword(int userId, string newPassword)
  2. AddEmailToUserAccount(int userId, string email)
  3. ChangeProfilePicture(int userId, Image image)
  4. ...

Now, I don't know which method is truly better, and for each approach, we encounter problems. I mean, I'm going to design the infrastructure for a new system, and I don't have enough reasons to pick any of these approaches. I couldn't find good resources on the Internet, because of the lack of keywords I could provide. What approach is better? What pitfalls each has? What benefits can we get from each one?

1
  • It will depend on how big is your entity. Definitely having one update per entity is better. I would look at so called bigger entities and try to split them to sections. That may definitely help.
    – Yusubov
    Jun 24, 2012 at 13:23

3 Answers 3

8

Your question amounts to picking the right abstraction level for your service's interface. Every real-world system has different requirements, but as a general rule I strongly prefer service interfaces having a high level view. That is, the second approach you describe, not the first.

However, this is just a guideline and to make a sound choice you will need to be able to justify it with more than "some guy said it was better this way" or "it's the usual approach".

So let's dig down to the relevant issues here.

Coupling

Your design should allow the service implementation and its clients to be loosely coupled. The client should not need to know how the service is implemented, or how it stores (often, even how it represents) its data. It should be possible to (greatly) change the implementation of the service without needing to rebuild or modify the clients. The use of the word "Entity" in your description seems to imply that the objects exposed via the service interface correspond exactly to the entities into which the application's data has been decomposed. If so this may indicate that there is tight coupling between the client interface and the service implementation. Sometimes this is allowable though, where the service itself uses data definitions defined in its own interface (this can help with decomposition, which I will mention again later).

Think about how you would roll out a change to the data structures used in the interface; that is, if the data structures carried across your service interface need to change, must you perform a big-bang change (awkward!) or can your service design accomodate clients some of which expect the new representation and some the old? (interface versioning is a common way to allow this)

Decomposition

The service's interface design should generally allow enough flexibility that the service implementation can be split up in order to allow part of the service implementation to migrate to a different design. For example, it should be possible for the maintenance of people's attributes to be moved out of the old service implementation and into an LDAP database. While I suppose this is always possible, I mean that the design should work in such a way that the clients don't necessarily need to care that this happened, and that the implementation of only a reasonable amount of the service's interfaces should need to be changed to allow this. Phrasing this differently, this kind of refactoring should not require changes to be made in unrelated parts of the service implementation.

Indirection

Your service's level of abstraction should be chosen in such a way that it will interact in an easy-to-explain way with layered systems. For example, it should be straightforward to create a caching service which front-ends this service and caches data to reduce load on the back-end. Likewise with modifications to the authorisation scheme. Equally, it should be reasonably simple to create a mock back-end (often only offering a subset of interfaces) to facilitate testing of clients. Likewise, artificial clients for load-testing.

Other Issues

There are other issues to bear in mind for some systems which aren't always important, but can make life very hard if you don't think of them ahead of time. A big one here is the question of how to shard the service. That is, split the existing system into several parts, each of which deals with some of the data objects in the system. For example if your system accepts email, you may need to split it into multiple backends, each accepting email for a subset of users (e.g. "a"-"f" on shard 1, "g"-"p" on shard 2, "q"-"t" on shard 3, "u"-"z" on shard 4, "Â"-"Ͱ" on shard 5, and so on). In order to allow the clients to remain unchanged, this kind of change would also usually be accompanied by the introduction of a proxy layer, whose only job is to forward requests to the relevant backend (it should be possible to deploy as many proxies as are needed to cope with the load; their task is not computationally hard and it shouldn't require hitting a disk to identify the right backend).

0
2

The second approach to updates provides more semantic meaning to the update. It is similar to CQRS (Command Query Responsibility Separation) where you provide a different service for Commands (changes to the model) versus Queries against the model. Using this approach, you can mitigate the need to handle optimistic concurrency because instead of one global "this object was updated" operation, you provide distinct operations that modify specific pieces of information.

This approach also provides a good entry point for more detailed auditing. Instead of just seeing User A updated Customer X, you can say User A changed Customer X's primary billing address.

I'd also recommend using an O/RM that provides change tracking out of the box so that you just load the entity, make the change and save it. You can also leverage domain driven design and put these operations directly on the entity. So instead of having a ServiceOperation that manipulates the entity, the Service Layer becomes a façade that delegates the operation to the entity directly.

Using this approach, the details of your underlying model can change without requiring the Service Layer or its clients to change.

0
0

Unless you are using an ORM to track changes, it would be difficult to pass only the changed columns simply because there are many permutation of parameters. You'd need to write a separate method for each permutation. This is clearly not possible.

Example:

Customer (id, firstName, lastName)

To update a customer record, you could have the following parameters passed to UpdateCustomer (and its overloads):

A-firstName

B-firstName, LastName

C-LastName

This is not an acceptable approach specially with large number of columns.

Passing the entire entity also has its drawbacks however, those can be tolerated:

  1. It is error prone. You must ensure that each column holds correct data.

  2. It passes data not needed and will require an update statement with unnecessary SET statements. Also, you may need to perform unnecessary lookups to other tables to populate values in FKs and do unnecessary validations.

  3. In some applications, it is not OK to update all data in an entity, since some users don't have the privilege to do so. This is true for banking applications for example. In this case, you have to pass the changed columns only otherwise the update for non-privileged users will not go through.

So, unless you are willing to write a layer that would detect changes and generate SQL for it, the best approach (in my opinion) is to use an ORM that track changes for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.