Closed joaorod closed 8 years ago
First of all, let me start by reminding that Layered Architecture != Different Assemblies. I've met so many "Enterprise Apps" that has tens of assemblies while they do nothing with that level of abstraction. If some "functionality" is only going to be used once, trying to abstract it goes into YAGNI.
Now you have two applications, web and executable. So you probably don't want to reference Web assemblies from EXE. Even though i'd recommend your EXE to access database through your web services, it's also possible to get Row + Repository to a separate assembly. There are some projects with that structure i use.
Primary problem is attributes you talk about but they are bare classes with no reference to actual implementation. Having them on a row brings no extra dependency to your Row class, other than the assembly they are defined in which is usually Serenity.Core.
You have to move ClientTypes.tt to project that has your row classes for this reason. It's a little price to pay to have things in one place. It would be nice to decorate a row in another assembly but unfortunately we don't have way to do it with intellisense and compile time checking. And i don't like losing my way among hell of folders and projects to just change display name for a field.
Oh and meanwhile, here is a sample of enterprise software :)
https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition
Thank you Volkan.
I’m watching this project and amazed by both the quantity and quality of your responses.
About this one, bulls-eye again.
I know that Layered Architecture != Different Assemblies, but you do well pointing this out.
I’ve some strict performance requirements for that EXE so I’ll try to avoid accessing the database through web services.
I did not want the EXE needed depend on “*.Web” assemblies since it should be able to run without all that, yet I’m not fundamentalist about this.
I’ll follow your suggestion and after that I’ll close this issue with a small explanation what I’ve done.
Thank you again and keep the good work.
PS: I did not know the “FizzBuzzEnterpriseEdition” that you pointed. Funny stuff. Maybe with the help of a dozen more engineers it could be a little more Enterprisish :)
Hi, As promised, here is an explanation of what I’ve done to extract the database access parts to a separated assembly.
Although the detailed explanation is long (sorry for that), the actual implementation is straightforward. The first idea is that Repository and Row objects where moved to a separated assembly (AppModel.dll).
To illustrate what I’ve done, I’ll use some very basic structure:
After generating all the code with Sergen, and some changes to add Lookups, computed fields, etc.… we end up with two folders [Client] and [ClientAccount] The Repository.cs and Row.cs files are moved to AppModel project. Everything else remains on Web Project. After that, the transform of T4 templates will not be able to work correctly. I was not able to perform the Volkan suggestion (as I understand it) of moving ClientTypes.tt to the project that has the Row classes. I’ve tried that but I was not able to do that without ending up with a mess. I’ve followed a different (and maybe questionable) approach: Row classes: It’s not a problem for me that Row classes in AppModel assembly have some annotations, even UI related, to maintain simplicity. Yet, to avoid problems with T4 templates, it must be also another “Row” class in Web project. That “Row” class could inherit from the existing “Row” in AppModel. So after this point I’ll call AppModel’s Row as the RowBase and the Web “Row” simply as Row. Since we now have two classes we can put (if we want RowBase to be a little more “clean”) the stuff related to UI/Web in Row object. So, what to put in “Row” and what to leave in RowBase? “My” criteria were:
Is also needed to split the inner RowFields object. The approach is the same as before, the Row.RowFields class will inherit from RowBase.RowFields. Also we need RowBase to have two constructors, one (parameterless) to be called directly and another to be called by it’s derivative.
Example of ClientAccountRow :
Example of ClientRow (In this simplest case, the Row class is only a “way” for T4 templates to work, since all stuff is in RowBase)
After Row, we must also make some changes in Repository Object since the Web Controllers are expecting “MyRepository“’s Row types to match the “MyRow” type. For that we can also split the Repository object between Model and Web projects using inheritance, and in some cases more complex, can be done. Yet, in this simple cases, making Repository a generic class should be enough.
With this we can now use in Controller :
Sorry again for the long post.
Please let me know what you think of this.
Thanks for sharing and detailed explanations @joaorod. This is also a possible. I understand that it is a bit complex to move ClientTypes.tt like i suggested, even though it's clear that you have good .NET skills looking at your solution.
So i wanted to throw a quick proof of concept sample, that might help you or others who needs this.
https://github.com/volkanceylan/SereneLayers
Most rows / repositories are moved into Business project. It was a quick test so there might be some things i missed, but it looks to be working atm.
Thank you @volkanceylan!
That sample will surely help me and others.
After taking a look, I see that I was very close to get it working :). I'was missing some path changes in ClientTypes.tt and CodeGenerationHelpers.ttinclude files.
But, because of that difficulty I end up with that alternative approach.
IMO, in that alternative the parts can be a little more separated in terms of what belong to each layer. Yet it comes with a cost since looses a bit of clarity/simplicity that is one of the serenity strengths.
In the sample you shared all remain simple and clear, yet, separated as intended, so, maybe I'll follow it.
There is a problem with the implementation I've suggested since can cause problems with RowFields indexes (serialization can fail to put the values on correct fields, for example).
That can be avoided creating an instance variable on RowBase to be used on local getters and setters.
So, using the same example, the previous version is on left and the corrected one is on the right.
Edit:: More clean way :
Rename the existing Fields static to BaseFields and create the instance variable with Fields name. this way all the gets/sets remains the same :
@joaorod just curious about this because I see you are running a windows service engine , I need to do something similar with getting reports to be generated the database being in the back end and serenity the front end. However I need to have some nightly reports generated automatically and emailed out - so my thought was to hit the service end of serenity and push those out. Do you have any advice , examples or links to reading ?
Hi,
I'm creating an application that has two parts, an engine (windows service) and a management portal (powered by serenity)
These two parts share the same database tables, and, the way that I was planning, the two parts should also share the same Data Access Layer (DAL), or at least part of it.
Before starting with serenity I was prototyping a DAL module using Entity Framework on a separate assembly, (AppModel.dll).
I'm now trying to do the same with serenity, starting moving the Serenity "DAL" parts to a separated assembly (AppModel.dll)
AppModel.dll (referencing : Serenity.Data + Serenity.Data.Entity,...)
AppPortal.Web (referencing : AppModel.dll, Serenity.*)
AppEngine.exe (referencing : AppModel.dll)
I'm some kind worried I'll end up bending to much the separation of concerns that I'm after.
At first sight, my big doubt is about TableXRow. Seams to me that some parts should not belong to DAL assembly AppModel.dll. Stuff like LookupEditor, DisplayName, SetFieldFlags, etc. should belong to UI and not DAL.
Any advise how should/can I do that from architectural level before I start bringing down the house ?
Thank You.