Today's essay deals with the tricky issue of custom features for individual customers who are running instances of your software.
The question comes by way of a regular reader who prefers to remain anonymous, but asks this:
... I work on a large (to me, anyway) application that serves as a client database, ticket system, time-tracking, billing, asset-tracking system. We have some customers using their own instances of the software. Often, those customers want additional fields put in different places (e.g., a priority column on tickets). This results in having multiple branches to account for versions with slight changes in code and in the database. This makes things painful and time-consuming in the long run: applying commits from master to the other branches requires testing on every branch; same with database migrate scripts, which frequently have to be modified.
Is there an easier way? I have thought about the possibility of making things "optional" in the database, such as a column on a table, and hiding its existence in the code when it's not "enabled." This would have the benefit of a single code set and a single database schema, but I think it might lead to more dependence on the code and less on the database -- for example, it might mean constraints and keys couldn't be used in certain cases.
Restating the Question
Our reader asks, is it better to have different code branches or to try to keep a lot of potentially conflicting and optional items mixed in together?
Well, the wisdom of the ages is to maintain a single code branch, including the database schema. I tried exactly once, very early in my career, to fork my own code, and gave up almost within days. When I went to work in larger shops I always arrived in a situation where the decision had already been made to maintain a single branch. Funny thing, since most programmers cannot agree on the color of the sky when they're staring out the window, this is the only decision I have ever seen maintained with absolute unanimity no matter how many difficulties came out of it.
There is some simple arithmetic as to why this is so. If you have single feature for a customer that is giving you a headache, and you fork the code, you now have to update both code branches for every change plus regression test them both, including the feature that caused the headache. But if you keep them combined you only have the one headache feature to deal with. That's why people keep them together.
Two Steps
Making custom features work smoothly is a two-step process. The first step is arguably more difficult than the second, but the second step is absolutely crucial if you have business logic tied to the feature.
Most programmers when confronted with this situation will attempt to make various features optional. I consider this to be a mistake because it complicates code, especially when we get to step 2. By far the better solution is to make features ignorable by anybody who does not want them.
The wonderful thing about ingorable features is they tend to eliminate the problems with apparently conflicting features. If you can rig the features so anybody can use either or both, you've eliminated the conflict.
Step 1: The Schema
As mentioned above, the first step is arguably more difficult than the second, because it may involve casting requirements differently than they are presented.
For example, our reader asks about a priority column on tickets, asked for by only one customer. This may seem like a conflict because nobody else wants it, but we can dissolve the conflict when we make the feature ignorable. The first step involves doing this at the database or schema level.
But first we should mention that the UI is easy, we might have a control panel where we can make fields invisible. Or maybe our users just ignore the fields they are not interested in. Either way works.
The problem is in the database. If the values for priority come from a lookup table, which they should, then we have a foreign key, and we have a problem if we try to ignore it:
- We can allow nulls in the foreign key, which is fine for the people ignoring it, but
- This means the people who require it can end up with tickets that have no priority because it does not prevent a user from leaving it blank.
A simple answer here is to pre-populate your priority lookup table with a value of "Not applicable", perhaps with a hardcoded id of zero. Then we set the default value for the TICKET.priority to zero. This means people can safely ignore it because it will always be valid.
Then, for the customer who paid for it, we just go in after the install and delete the default entry. It's a one-time operation, not even worth writing a script for, and it forces them to create a set of priorities before using the system. Further, by leaving the default of zero in there, it forces valid answers because users will be dinged with an FK violation if they do not provide a real priority.
For this particular example, there is no step 2, because the problem is completely solved at the schema level. To see how to work with step 2, I will make up an example of my own.
Step 2: Unconditional Business Logic
To illustrate step 2, I'm going to make up an example that is not really appropriate to our reader's question, frankly because I cannot think of one for that situation.
Let's say we have an eCommerce system, and one of our sites wants customer-level discounts based on customer groups, while another wants discounts based on volume of order -- the more you buy, the deeper the discount. At this point most programmers start shouting in the meeting, "We'll make them optional!" Big mistake, because it makes for lots of work. Instead we will make them ignorable.
Step 1 is to make ignorable features in the schema. Our common code base contains a table of customer groups with a discount percent, and in the customers table we make a nullable foreign key to the customer groups table. If anybody wants to use it, great, and if they want to ignore it, that's also fine. We do the same thing with a table of discount amounts, we make an empty table that lists threshhold amounts and discount percents. If anybody wants to use it they fill it in, everybody else leaves it blank.
Now for the business logic, the calculations of these two discounts. The crucial idea here is not to make up conditional logic that tries to figure out whether or not to apply the discounts. It is vastly easier to always apply both discounts, with the discounts coming out zero for those users who have ignored the features.
So for the customer discount, if the customer's entry for customer group is null, it will not match to any discount, and you treat this as zero. Same for the sale amount discount, the lookup to see which sale amount they qualify doesn't find anything because the table is empty, so it treats it as zero.
So the real trick at the business logic level is not to figure out which feature to use, which leads to complicatec conditionals that always end up conflicting with each other, but to always use all features and code them so they have no effect when they are being ignored.
Conclusion
Once upon a time almost everybody coding for a living dealt with these situations -- we all wrote code that was going to ship off to live at our customer's site. Nowadays this is less common, but for those of us dealing with it it is a big deal.
The wisdom of the ages is to maintain a common code base. The method suggested here takes that idea to its most complete implementation, a totally common code base in which all features are active all of the time, with no conditionals or optional features (except perhaps in the UI and on printed reports), and with schema and business logic set up so that features that are being ignored simply have no effect on the user.
92 comments:
Of course, one has to be careful that new features do have zero effect when being ignored. The repeated pattern I've encountered over the years is 'why has this batch process gone up by 25%' - and it turns out to be because it's doing a simple query that does nothing . . . 500,000 times.
(Back to the problems of encapsulation vs optimising loops).
Also, when it comes to UI design, my preference is for either leaving the field there (to be ignored) or greyed out if disabled. Actual invisibility either leaves odd gaps in a layout, or some kind of inevitably inferior auto-layout.
(The big exception is columns in a data grid)
Should add that the 'do nothing 500,000 times' example isn't a DBMS problem. The query itself could take 0 msecs, it's the 500,000 IPC or network calls that are the problem!
Ken, I gotta say I like your solution enough to try it out next time I'm on that situation.
I do have two comments though regarding the two alternatives.
I think maintaining separate branches is becoming easier with modern SCM tools and dev practices. At the very least, good merge tools, continuous integration, and good unit test coverage (in terms of meaningful coverage, not just running through all your branches) make the process more mechanical.
With regards to custom db deploy scripts, who writes deploy scripts manually anymore? On the Microsoft side of things, higher end versions of Visual Studio, or Redgate SQL compare will let you merge schemas.
Yes, sometimes you have to edit the script, especially if there is a need to do some DML, I personally find that to be rare in practice. I am very "write it by hand" when it comes to DDL scripts on the dev machine, but I just don't see the sense in doing subsequent deploys towards production.
"Code that was going to ship off to live at our customer's site" has become code that is going to live at a single-tenant SaaS app. But the principles are the same.
We are using one table which is used to switch off or switch on different functionality. Also have one code base, without branching. Maintain "regular" code, from my point of view, is different than maintain database code. There is one more variable which do not exists in "regular" code, and exists in databases. This is time. And data, as a consequence of time. It means that it is possible to get latest version of "regular" code, and deliver it, but database should be upgraded(I regard here schema and data changes). So, the same mechanism can not be applied.
> There is some simple arithmetic as to why this is so. If you have single feature for a customer that is giving you a headache, and you fork the code, you now have to update both code branches for every change plus regression test them both, including the feature that caused the headache. But if you keep them combined you only have the one headache feature to deal with. That's why people keep them together.
I debated posting a reply to this, and took some time to mull over exactly what I wanted to say.
First, you are right. At the surface, it is simple arithmetic.
But the underlying problem of having a feature implemented two different ways for two separate clients still exists, and gets worse as the number of clients approaches infinity. Computer scientists specializing in software engineering have a name for the subfield dedicated to studying this problem: Software Factories and Software Product Lines. And these things can get very complicated. For example, ontological mapping becomes an issue. If you use the word "contract number" with the wrong client, then they will give you an insurance contract number, rather than the global insurance plan id. Or vice versa: Use the word "insurance plan" and they give you contract level details instead. It all depends on how their accounting subsystem was set-up. These sorts of ontological issues were covered in William Kent's fantastic book, Data and Reality (1977).
Now, for a more practical view:
I think if you have a multi-tenant database and many clients, and in that database you have a table(s) with sparsely populated columns representing what features each client has flipped on, then you are begging for trouble. The pricing of your software simply will not work unless it is ridiculously expensive. I may be mistaken, but this is what happens with most shrinkwrapped and semi-extensible ERP/CRM solutions like Peoplesoft, SAP, Meditech, Epic, VPM, and so on.
In your Step 1 section, you said,
Further, by leaving the default of zero in there, it forces valid answers because users will be dinged with an FK violation if they do not provide a real priority.
Are you sure you didn't mean that if you delete the default it forces them to provide a real priority? Because if you leave the default, that customer doesn't have to provide a priority, wasn't that the point of the default?
thats one of the best article for us in the domain
The questioner says this happens 'often' so maybe they should consider refactoring the system to use an EAV db schema.
Please visit my blog, I'll be publishing C++ tutorials during the summer! http://programming-blog.com
Thanks! :)
a good concept. ignore the redundancy ... please visit more database concept www.database-concept.com
I have actually gone the other way to some extent, and seen the db as a platform where functionality can be added. Although I think your approach works very well in many cases, there are cases (like ERP systems) where you are essentially shipping a framework, and that framework may have a lot of features very few customers need.
In these cases, it makes sense to follow something like your advice but also modularize so you dont have to ship the whole product to everyone.
hey ken,
I havent spoke to you in a while. I met you through donald Organ and tom melendez. you had worked on my site 'circuits" a few years back. I need a little advice as i move forward with some stuff and i figured you would be the guy that could point me in the right direction..anyway if you get a few minutes to spare could you shoot me an email with your contact info on it. I appreciate it. thank you..
john jedi4425@gmail.com
hey ken,
i havent spoken to you in a while about programming. i was introduced to you by donald organ and tom melendez...you worked on "circuits" for me a few years back. I was wondering if you could shoot me an email when you get a quick chance. i could use a little advice as i proceed with my project..thanx john
jedi4425@gmail.com
Prediksi Bola | Judi Online | Bursa Taruhan Casino | Taruhan Bola
Judi Bola Terpercaya | Bandar Bola Online | Agen Taruhan
Prediksi Bola | Judi Online | Agen Casino | Taruhan Bola
Casino Online | Agen Bola | Prediksi Dan Berita Sepakbola
Agen Bola Online | Bandar Bola | Agen Casino| Situs Judi
Agen Bola | Taruhan Online | Bandar Judi Terpercaya
Agen Bola | Taruhan Bola Online | Agen Casino Terpercaya
Taruhan Bola | Judi Online | Agen Casino Terpercaya
Agen Bola Online, Situs Judi Casino Online, Bandar Taruhan Bola
Agen Casino | Judi Online | Bursa Taruhan | Bandar Bola
Agen Judi Bola | Bandar Bola Online | Casino Online
Informasi Terupdate | Sumber Berita | Sumber Inspirasi
Berita Terbaru | Inovatif
Berita Bola | Prediksi Bola Jitu
Hanya Untuk Anda Pecinta Dunia Maya
Info Masa Depan | Ulas Bola Hari Ini
Info Terupdate | Berita Inovatif | Berita Pilihan
Prediksi Skor | Berita Bola | Jadwal Bola
Prediksi Bola | Pasaran Bola | Berita Bola Online
Informasi Terkini | Prediksi Dan Berita Sepakbola
Info Berita Terbaik | Bursa Taruhan
Berita Terhangat | Info Masa Depan
Berita Terupdate | Sumber Berita
Hasil Skor | Prediksi Bola Terpercaya
agen texas poker dan domino
bola pelangi agen bola sbobet
Judi Online
Taruhan Bola
Agen Casino
Bandar Bola
Agen bola, poker, judi on-line, Prediksi bola, togel
cara mengatasi berbagai macam penyakit secara alami
Obat Herbal Penurun Darah Tinggi Obat Herbal Ambeien Akut Obat Herbal Batu Empedu Tanpa Operasi Obat Herbal Sinusitis Kronis Obat Herbal Tumor Otak Jinak Obat Herbal Wasir Kronis Tanpa Operasi Obat Herbal Liver Kronis Paling Ampuh Obat Herbal Bronkitis Kronis Obat Herbal Usus Buntu Tanpa Operasi Obat Herbal Eksim Paling Ampuh Obat Herbal Gagal Ginjal Kronis
pengobatan penyakit glaukoma secara alami tanpa operasi
Obat Herbal Glaukoma
cara alami mengatasi penyakit kanker hati tanpa operasi
Obat Herbal Kanker Hati Tanpa Operasi
your post is very interesting
thank you for sharing
i really liked
Obat Herbal TBC
excellant post
Obat Herbal Polip Hidung
Hi,
its a very helpful information.
A well-managed email database is by far the best way to boost sales without risking budget waste because it appeals to consumers who have already opted in to receive marketing information from you. Germany Mailing Lists
I would like to appreciate this nice article. I am waiting for this kind of informative interesting blog post. You ma find best poker table : Custom Poker Table
This is very nice article. When i was searching for primary care doctor in Bangalore i got The Family Doctor centre. It is also a best hospital in Bangalore. For more information please go through the link
gclub casino online
casino online
gclub online
Hi sir,
This article is very much helpful. But still I have question on it should I ask here ?
Wow..!! This is an adorable post as the information shared in this post is very helpful. I am thankful to get this post when I was searching a web developer and mobile app developer
Somebody essentially help to make seriously posts I would state. This is the very first time I frequented your web page and thus far? I surprised with the research you made to create this particular publish incredible. Great job!Import Project Forecasts
Hey, very nice site. I came across this on Google, and I am stoked that I did. I will definitely be coming back here more often. Wish I could add to the conversation and bring a bit more to the table, but am just taking in as much info as I can at the moment. Thanks .
DedicatedHosting4u.com
Inti dari permainan ini adalah, jeli membaca kondisi dan kartu. Yang paling jeli, dialah yang akan keluar sebagai pemenang. Keberuntungan tidak lah menjadi faktor utama lho dalam permainan ini
asikqq
pionpoker
dewaqq
bandar ceme
sumoqq
hobiqq
paito warna
interqq
forum prediksi
Are you tired of being human, having talented brain turning to a vampire in a good posture in ten minutes, Do you want to have power and influence over others, To be charming and desirable, To have wealth, health, without delaying in a good human posture and becoming an immortal? If yes, these your chance. It's a world of vampire where life get easier,We have made so many persons vampires and have turned them rich, You will assured long life and prosperity, You shall be made to be very sensitive to mental alertness, Stronger and also very fast, You will not be restricted to walking at night only even at the very middle of broad day light you will be made to walk, This is an opportunity to have the human vampire virus to perform in a good posture. If you are interested contact us on Vampirelord7878@gmail.com
Yws
Nice Blog
Yaaron Studios is one of the rapidly growing editing studios in Hyderabad. We are the best Video Editing services in Hyderabad. We provides best graphic works like logo reveals, corporate presentation Etc. And also we gives the best Outdoor/Indoor shoots and Ad Making services.
video editors studio in hyderabad
short film editors in hyderabad
corporate video editing studio in hyderabad
ad making company in hyderabad
Good Information
"Pressure Vessel Design Course is one of the courses offered by Sanjary Academy in Hyderabad. We have offer professional
Engineering Course like Piping Design Course,QA / QC Course,document Controller course,pressure Vessel Design Course,
Welding Inspector Course, Quality Management Course, #Safety officer course."
Piping Design Course in India
Piping Design Course in Hyderabad
Piping Design Course in Hyderabad
QA / QC Course
QA / QC Course in india
QA / QC Course in Hyderabad
Document Controller course
Pressure Vessel Design Course
Welding Inspector Course
Quality Management Course
Quality Management Course in india
Safety officer course
BA Exam Result - BA 1st Year, 2nd Year and 3rd Year Result
Bsc Exam Result - Bsc 1st Year, 2nd Year and 3rd Year Result
Complete the Manual setup of Roku Streaming Device and once you complete the process go for the given steps on your system or mobile.
Steps to be done on the system
1.Now, open the system and click on the browser of your choice
2.Navigate to the Roku.com/link
3.You will find the space to type the code
4.Subsequently, type the alphanumeric code on the space and hit on the submit overlay
5.This should activate your Roku device
For any further queries on Roku com link Activation, feel free to contact our active customer care team, working round the clock at the toll-free number.
best makeup artist in gurgaon
best makeup academy in gurgaon
Www.webroot.com/safe
Online Download Www.webroot.com/safe
Install Www.Webroot.Com/Safe With Key Code
Webroot.com/safe
If you would like to know the features and specifications of the Roku remote app, the steps to activate Roku using then it's good to read the latest articles and blog posts in our page @ Roku.com/link Activation code
porno video
What exactly is Putlocker?
If you've made it so far without perceiving what it is, here's a straightforward rundown of what Putlocker is: Putlocker is an online stage that allows you the opportunity to stream movies and shows television to no end.
No sign up, no Mastercard, that's needed straightforward. Essentially visit the site, type what you need to look for and you're good to go. It's one of several Putlocker hd
Beginning late, Putlocker can be known as GoMovies. The two destinations are in any case essentially the proportionate, they simply use a substitute zone and name.
How does Putlocker do its work?
Close. Close. Rather than promoting the video recording itself, what Putlocker is doing is going on as a step that allows you access to accounts from well-known cyberlockers around the world. Significant
Note: The default regions for Dragon User Profiles are recorded early.
Duplicate the facilitator of the User Profile.
Note: The facilitator name will be the name of the User Profile.
Glue the User Profile facilitator to another region, (for example, the Windows Desktop) to trade that User
nuance dragon technical support.
To genuinely import a User Profile:
Open a Windows Explorer window.
Break down to the record where the Dragon User Profile(s) were emphasized to.
Duplicate the organizer of the User Profile.
Note: The organizer name will be the name of the User Profile.
Break down to the territory where the Dragon User Profiles are overseen.
Note: The default regions for Dragon User Profiles are recorded early.
Glue the User Profile facilitator to this region to import that User Profile
Thanks for your ideas. You can also find the details on Affity Solutions
HINDISONGS
putlocker one of the great streaming systems, in which you can watch special films and tv series. Moreover, you may down load various motion pictures of your choice and watch it while you want to observe it subsequent.
At a time, the channels have been close down and plenty of humans had been no longer able to watch the favorite films in their desire. Because of the lack of ability to benefit get right of entry to to their internet site, many human beings have to look for the alternative. Incidentally, there are plenty of alternatives available for you.
You can test the numerous alternatives supplied to you here and use any of them to watch movies on putlockers , tv series, and most significantly, you can down load films of your preference.
You can watch on line movies and also you should make certain that you are secure while watching it. To be safe way that the film website will now not be harm on your machine with viruses. This is one of the risks users encounter in the sort of web page. The possibility of encountering viruses is constantly there.
Before you start to get right of entry to that site, you must make sure that you do not disclose your PC or your telephone or any of the devices you want to watch the video. If you need to download movies, you ought to additionally use sturdy antivirus to ensure which you do no longer down load any film that can damage your machine.
This may be very vital, specifically in case you want to benefit get right of entry to to a free website like maximum of them which might be encouraged here.
Furthermore, to make certain which you live safe, you ought to make certain that you examine the website's guidelines and do now not cross contra to the regulations put in area. Ensure that you do not destroy any rule you can get prosecuted in case you go towards the policies.
Putlocker is an online stage that allows you the opportunity to stream movies and shows television to no end.
UPLAY365
ظهور نخستین سایت های شرط بندی
اما نخستین سایت های شرط بندی چگونه شکل گرفتند؟ قبل از اینکه به مبحث اصلی مقاله یعنی معرفی سایت بازی انفجار نیترو بپردازیم، بد نیست کمی با ظهور نخستین سایت های شرط بندی آشنا شویم. اگر فراموش نکرده باشید اشاره کردیم بازی های شرط بندی در گذشته محدودیت های زیادی را داشتند.
حضرات نیترو
حضرات
بازی رولت
اما امروزه قدرت اینرنت باعث شده تا این بازی به سایت های شرط بندی کوچ کنند و از محدودیت مکانی خارج شوند. تاریخ دقیق ظهور سایت های شرط بندی در دسترس نمی باشد.
بتخته نرد آنلاین نیترو
تخته نرد آنلاین
بلک جک نیترو
اما با رجوع به تاریخچه بعضی از این سایت های از جمله سایت نیترو، خواهیم دریافت که پیشرفت تکنولوژی، بزرگترین ترغیب کننده برای ایجاد این سایت ها بوده است. البته مطالب گفته شده تمامی اطلاعات درباره این سایت های نمی باشد.
پوکر آنلاین نیترو
پوکر آنلاین
بلک جک
شمار سایت های شرط بندی امروزه رو به افزایش می باشد. برای مثال می توانیم به سایت نیترو اشاره کنیم. این سایت معتبر یکی از پیشگامان در این عرصه می باشد که قصد داریم در این مقاله شما را بیشتر با آن آشنا کنیم.
پاسور آنلاین نیترو
پاسور آنلاین
ترفند برد و آموزش بازی انفجار آنلاین و شرطی، نیترو بهترین و پرمخاطب ترین سایت انفجار ایرانی، نحوه برد و واقعیت ربات ها و ...
Visit https://www.wmsociety.org/
here for more information
Hello! Thanks for sharing this amazing content your information is really very awesome to read. Keep it up and best of luck for your future updates.
Thanks for the article…
spa in dubai
spa in sharjah,
massage in dubai,
massage in sharjah,
massage center in dubai,
massage center in sharjah
spa massage in Dubai
full body massage in spa in sharjah
This is a very good content and thank you for sharing such an wonderful information. Please follow my website for more information in Advanced SEO Training in Kolkata.
Digital Marketing Training.
Digital Marketing training institute.
Digital Marketing Agency in Kolkata.
Digital Marketing Services in Kolkata.
Advanced Digital Marketing Courses.
SMM Training in Kolkata.
SEO Training in Kolkata.
Excellent Content. I am following your blog on a regular basis. Thank you for sharing this. Please follow my website for more information in Mobile Repairing Training Course.
Mobile Repairing Training in Kolkata.
Bike Repairing Training in Kolkata.
TV Repairing Training in Kolkata.
AC Repairing Training in Kolkata.
Computer Repairing Training in Kolkata.
AC Repairing Training in Kolkata.
CCTV Repairing Training in Kolkata.
Very nice information. Really appreaciated. Thank you for the details. Please follow my site for PLC Training in Kolkata.
PLC SCADA Training in Kolkata.
PLC Training in Kolkata.
SCADA Training in Kolkata.
HMI Training in Kolkata.
VFD Training in Kolkata.
Industrial Automation Training in Kolkata.
Please keep sharing this types of content, really amazing. Please follow my website for more information in Best IT Professional Courses in Kolkata.
ANSIBLE Training in Kolkata.
DevOps Training in Kolkata.
CKA Training in Kolkata.
CKAD Training in Kolkata.
Docker Training in Kolkata.
Kubernetes Training in Kolkata.
AWS Training in Kolkata.
AZURE Training in Kolkata.
VMWARE Training in Kolkata.
Citrix Training in Kolkata.
NSX Training in Kolkata.
VDI Training in Kolkata.
You make so many great points here that I read your article a couple of times. Your views are in accordance with my own for the most part. This is great content for your readers.
Best software development Company in dubai
You there, this is really good post here. Thanks for taking the time to post such valuable information. Quality content is what always gets the visitors coming.
Best Digital Marketing Companies in dubai
Wow! Such an amazing and helpful post this is. I really really love it. It's so good and so awesome. I am just amazed. I hope that you continue to do your work like this in the future also
Best Web Development Companies in dubai
I was surfing the Internet for information and came across your blog. I am impressed by the information you have on this blog. It shows how well you understand this subject.
Web Development Agency Dubai UAE
I was reading some of your content on this website and I conceive this internet site is really informative ! Keep on putting up.
Software Development Company in Dubai UAE
This is such a great resource that you are providing and you give it away for free. I love seeing blog that understand the value of providing a quality resource for free.
Mobile App Development Company in Dubai
Positive site, where did u come up with the information on this posting? I'm pleased I discovered it though, ill be checking back soon to find out what additional posts you include.
Software Development Company in Dubai UAE
I wanted to thank you for this great read!! I definitely enjoying every little bit of it I have you bookmarked to check out new stuff you post.
Website Development Company in Dubai
Excellent article. Very interesting to read. I really love to read such a nice article. Thanks! keep rocking.
Ecommerce Development Company in Dubai
Thanks for a very interesting blog. What else may I get that kind of info written in such a perfect approach? I’ve a undertaking that I am simply now operating on, and I have been at the look out for such info.
Best Software Development Company in UAE
I admire this article for the well-researched content and excellent wording. I got so involved in this material that I couldn’t stop reading. I am impressed with your work and skill. Thank you so much.
UI UX Development Company in UAE
A very awesome blog post. We are really grateful for your blog post. You will find a lot of approaches after visiting your post.
Digital Marketing Companies in UAE
Pretty good post. I just stumbled upon your blog and wanted to say that I have really enjoyed reading your blog posts. Any way I'll be subscribing to your feed and I hope you post again soon. Big thanks for the useful info.
Digital Marketing Agency Abu Dhabi
At SynergisticIT we offer the best java bootcamp
This is very informative and the user friendly
https://www.shahzadmovers.com/movers-and-packers-in-sharjah/
Thank you very much for sharing useful information. I appreciate your efforts. Get
Top software development services
Best project management services in India
Techsaga Corporations optimally claim our Software development company in Noidaexpertise through the advanced involvement of our Software Product engineering team in devising the most assertive and advanced solutions for you at Tech saga. A software product development team has to perform in accord with the client's engineering team for ideal results.
Software development company in Noida team has to perform in accord with the client's engineering team for ideal results. Techsaga Corporations address this facet of software development process most aggressively with our streamlined and well-planned resources.
Are you looking for good hosting? So read Full Comparison of MilesWeb vs GoDaddy.
Movers and packers in Dubai
dubai movers and packers company
packers and movers service dubai
service basket uae
The Move Me
Great work. Thank you for being so supportive and solving my issue. I would recommend others to dial QuickBooks Customer Service 877-603-0806 for quick resolution of issues.
Thank you for sharing such a nice piece of content with useful information. Please also visit my site of Luxury Car Rental Dubai. We are offering special discount for everyone. You just need to use code of Online2021
Thanks
Renter Point is a leading platform for all type of rental cars in Dubai, Enjoy great deal on تأجير سيارات دبي from renterpoint.com.
Thanks for the excellent article, I enjoyed reading it! You will find out if you are ready to operate a supermarket. Because suffescom solutions living in a time of high contagion, the task of establishing a trustworthy security system like in-app development and any other services is essential to allowing clients to buy products from your app without being worried about being taken advantage of maximum result.
Thank you for sharing
Thanks For Your Blog
Nice Post Really Informative
Best App Development Companies
Best Mobile App Development Company
Educational Software Development Companies
Best mobile app development services
Custom application development company
Very nice blog and articles.
internship websites in india
internship acceptance letter
internship experience letter
Internship permission letter
internship courses
Internship offering companies
internship mail format
Internship program in chennai
Internship training online
internship and apprenticeship difference
How To Download USB Driver. HP provides the drivers necessary for your printer to work with the operating system on your computer. If your printer did not come with drivers, or you have added another computer to the office, you can get drivers directly from HP. The support section offered on the ..."
The article is very interesting. Thanks for sharing this article. I enjoyed reading your article.
internship for web development | internship in electrical engineering | mini project topics for it 3rd year | online internship with certificate | final year project for cse
Thanks for sharing the informative post. If you are looking the Linksys extender setup guidelines . so, we have a best technical expert for handlings your quires. for more information gets touch with us 토토
바카라사이트 Hi there! This is my first visit to your blog! We are a group of volunteers and starting a new project in a community in the same niche. Your blog provided us useful information to work on. You have done a extraordinary job!
You there, this is really good post here. Thanks for taking the time to post such valuable information. Quality content is what always gets the visitors coming. Feel free to visit my website; 안전놀이터
Having read this I believed it was extremely informative. I appreciate you spending some time and effort to put this informative article together. Feel free to visit my website; 토토
What a wonderful blog! I read your entire site and found it incredibly useful and easy to follow. A properly fitted leather dog collar should be lightweight and soft, allowing your dog to feel secure without being restricted. A basic collar with ID retention is also advised when additional movement equipment such as a strong dog leashes, harness or backpack is used.
If you want to create a social media marketing business, you must first become an expert in the operations and trends of the major social networks. It will assist if you have an active presence on social media yourself, so you can stay current on what works and what is currently hot. You'd probably start by focusing on small company clients who are too busy to manage their social media accounts and create social media management services strategies.
แหล่งรวม เกม PG SLOT พวกเราอาจจะไม่ยอมรับกันมิได้ว่า เว้นแต่เงินรางวัลที่เย้ายวนใจในเว็บไซต์พนัน pgslot แล้ว ภาพ เสียง ในเกมส์เป็นเรื่องสำคัญที่เย้ายวนใจเหล่านัดหมายพนันให้ได้เล่น
สะดวกกับการเล่นที่แปลกใหม่ กับเกมสล็อตค่าย PG มาให้ ทดลองเล่นสล็อต ถอนเงินได้จริง ทำได้ผ่าน สมาร์ทโฟน แท็บเล็ต และคอมพิวเตอร์ได้ตลอด 24 ชั่วโมง เหมาะกับเกมทุกรุ่น ลงเดิมพันการเล่นได้ตามต้องการ เล่นได้ทันทีผ่านเว็บไซต์ PGSLOTGAMES
เกมสล้อตรูปแบบใหม่ ทดลองเล่นสล็อต PG พร้อมทำกำไรได้อย่างหลากหลายรูปแบบ พร้อมเล่นได้เลยทันทีผ่านเครดิตฟรี ที่มีให้คุณได้รับสูงถึง 10,000 เครดิต ให้คุณสามารถนำไปเล่นเกมได้เลยแบบฟรีๆ เปืดประสบการณ์การเล่นอันเร้าใจ พร้อมให้คุณได้เล่นมากถึง 24 ชั่วโมง
คาสิโนออนไลน์ เว็ปพนันออนไลน์อัดับท็อปของประเทศไทย ที่สามารถเข้าทำเงินได้แบบ การันตีความปลอดภัยในการเล่น กับความทันสมัย mega game mega game เว็ปพนันทำเงินออนไลน์ ที่สามารถทำเงินได้แบบไม่มีเงื่อนไข.
It seems like I've never seen an article of a kind like . It literally means the best thorn. It seems to be a fantastic article. It is the best among articles related to 메이저안전놀이터. seems very easy, but it's a difficult kind of article, and it's perfect.
شركة كشف تسربات بالدمام
Sohbet Sohbet odaları
Mobil Sohbet Mobil sohbet odaları
Post a Comment